ElasticSearch 整合项目

项目整合ElasticSearch

上文说到ElasticSearch分词
本来简单介绍项目整合ElasticSearch

不建议使用spring boot中自带的依赖spring-data-elasticsearch:transport-api.jar

因为spring boot版本不同不适配es版本

如果只使用Httpclient,RestTemplate模拟发送HTTP请求,ES有很多操作要自己封装,麻烦

推荐使用ElasticSearch-Rest-Client官方RestClient,提供的API封装了ES操作

搭建环境

导入依赖

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-getting-started-maven.html

		<dependency>
            <groupId>org.elasticsearch.client</groupId>
            <artifactId>elasticsearch-rest-high-level-client</artifactId>
            <version>7.4.2</version>
        </dependency>

如果发现maven中导入的不是7.4.2版本,则可能是spring boot的dependence规定了elastic search版本去改成对应版本即可

在这里插入图片描述

初始化配置

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-getting-started-initialization.html

@Configuration
public class GulimallElasticSearchConfig {

    @Bean
    public RestHighLevelClient getRestHighLevelClient() {
        RestClientBuilder restClientBuilder = RestClient.builder(
                new HttpHost("服务器IP地址", ES开放的用户交互端口(我的是9200), "http"));
        
        return new RestHighLevelClient(restClientBuilder);
    }
}

配置RequestOptions

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-getting-started-request-options.html

ES说可以使用一个单例的类似配置请求头的RequestOptions,在发送请求时可以带上它

我们暂时复制下来,但是先不配置

@Configuration
public class GulimallElasticSearchConfig {
    private static final RequestOptions COMMON_OPTIONS;
    static {
        RequestOptions.Builder builder = RequestOptions.DEFAULT.toBuilder();
//        builder.addHeader("Authorization", "Bearer " + TOKEN);
//        builder.setHttpAsyncResponseConsumerFactory(
//                new HttpAsyncResponseConsumerFactory
//                        .HeapBufferedResponseConsumerFactory(30 * 1024 * 1024 * 1024));
        COMMON_OPTIONS = builder.build();
    }

    @Bean
    public RestHighLevelClient getRestHighLevelClient() {
        RestClientBuilder restClientBuilder = RestClient.builder(
                new HttpHost("47.108.181.237", 9200, "http"));

        return new RestHighLevelClient(restClientBuilder);
    }
}
测试增删改查
添加索引

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-document-index.html

官方文档提供了多种发送数据的方式,可以使用json字符串,map,内置生成json器等等…

在这里插入图片描述

常用方式是自己将对象转为json字符串再交给它

 	@Autowired
    RestHighLevelClient client;

    @Test
    void contextLoads() {
        //设置索引
        IndexRequest request = new IndexRequest("users");
        //设置文档id
        request.id("1");

        User user = new User("tcl", "广东", 20);
        //转为json格式
        String jsonString = JSON.toJSONString(user);

        //设置要发送的数据,格式
        request.source(jsonString, XContentType.JSON);

        //发送请求  并带上了RequestOptions(可以用来设置请求头)
        try {
            IndexResponse response = client.index(request, GulimallElasticSearchConfig.COMMON_OPTIONS);
            System.out.println(response);
        } catch (IOException e) {
            e.printStackTrace();
        }


    }

在这里插入图片描述

测试添加索引成功

查询

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-document-get.html

@Test
void get() throws IOException {
    GetRequest getRequest = new GetRequest("users", "1");
    GetResponse getResponse = client.get(getRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
    System.out.println(getResponse);
    //{"_index":"users","_type":"_doc","_id":"1","_version":2,"_seq_no":1,"_primary_term":1,"found":true,"_source":{"address":"广东","age":20,"name":"tcl"}}
}
修改

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-document-update.html

	@Test
    void update() throws IOException {
        UpdateRequest updateRequest = new UpdateRequest("users", "1");
        updateRequest.doc(JSON.toJSONString(new User("cl","四川",18)),XContentType.JSON);
        UpdateResponse updateResponse = client.update(updateRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        System.out.println(updateResponse);
        //UpdateResponse[index=users,type=_doc,id=1,version=3,seqNo=2,primaryTerm=1,result=noop,shards=ShardInfo{total=0, successful=0, failures=[]}]

        get();
        //{"_index":"users","_type":"_doc","_id":"1","_version":3,"_seq_no":2,"_primary_term":1,"found":true,"_source":{"address":"四川","age":18,"name":"cl"}}
    }
删除

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/7.x/java-rest-high-document-delete.html

    @Test
    void delete() throws IOException {
        DeleteRequest deleteRequest = new DeleteRequest("users", "1");
        DeleteResponse response = client.delete(deleteRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);
        System.out.println(response);
        //DeleteResponse[index=users,type=_doc,id=1,version=4,result=deleted,shards=ShardInfo{total=2, successful=1, failures=[]}]

        get();
        //{"_index":"users","_type":"_doc","_id":"1","found":false}
    }
检索查询

https://www.elastic.co/guide/en/elasticsearch/client/java-rest/current/java-rest-high-search.html

检索查询是比较常用的

经过上面的测试,操作API可以大致分为三个步骤: 设置请求,执行得到响应,查看响应

测试

//对年龄聚合,统计各年龄人数,并请求这些年龄段人的平均薪资
GET /bank/_search
{
  "query": {
    "match_all": {}
  },
  "aggs": {
    "ageTrems": {
      "terms": {
        "field": "age",
        "size": 10
      },
      "aggs": {
        "balanceAvg": {
          "avg": {
            "field": "balance"
          }
        }
      }
    }
  }
}

Java源代码:

@Test
    void searchQuery() throws IOException {
        //1.创建检索查询请求
        SearchRequest request = new SearchRequest("bank");

        //2.设置检索查询内容

        //2.1 设置查询所有内容
        SearchSourceBuilder builder = new SearchSourceBuilder();
        builder.query(QueryBuilders.matchAllQuery());

        //2.2 设置聚合内容
        //2.21 对年龄聚合,统计各年龄段人数
        TermsAggregationBuilder ageTrems = AggregationBuilders.terms("ageTrems").field("age").size(10);

        //2.22 对2.21的结果进行子聚合,求这些年龄段人的平均薪资
        AvgAggregationBuilder balanceAvg = AggregationBuilders.avg("balanceAvg").field("balance");
        ageTrems.subAggregation(balanceAvg);

        //将设置的内容放入builder
        builder.aggregation(ageTrems);
        //builder内容放入请求中
        request.source(builder);


        //3.执行
        SearchResponse response = client.search(request, GulimallElasticSearchConfig.COMMON_OPTIONS);

        //4.查看响应
        System.out.println(response.getTook());//6ms
        System.out.println(response.isTimedOut());//false
        // hits
        SearchHits hits = response.getHits();
        System.out.println(hits.getTotalHits());//1000 hits
        SearchHit[] searchHits = hits.getHits();
        for (SearchHit searchHit : searchHits) {
            System.out.println(searchHit.getIndex());//bank
            System.out.println(searchHit.getId());//1
            String jsonSource = searchHit.getSourceAsString();
            Account account = JSON.parseObject(jsonSource, Account.class);
            System.out.println(account);//Account(account_number=1, balance=39225, firstname=Amber, lastname=Duke, age=32, gender=M, address=880 Holmes Lane, employer=Pyrami, email=amberduke@pyrami.com, city=Brogan, state=IL)
        }

        // aggreagations
        Aggregations aggregations = response.getAggregations();
        Terms ageTremsResult = aggregations.get("ageTrems");
        List<? extends Terms.Bucket> buckets = ageTremsResult.getBuckets();
        for (Terms.Bucket bucket : buckets) {
            //key:年龄段 doc_count:该年龄段有多少人 balanceAvg:该年龄段平均薪资
            System.out.println("key: "+bucket.getKeyAsString());//key: 31
            System.out.println("doc_count: "+bucket.getDocCount());//doc_count: 61
            //拿到子聚合平均薪资
            Aggregations sunAgg = bucket.getAggregations();
            Avg subBalanceAvg = sunAgg.get("balanceAvg");
            System.out.println("balanceAvg: "+subBalanceAvg.getValue());//balanceAvg: 28312.918032786885
        }
    }

因为数据太多,遍历中的注释数据为第一个查询的数据

设置请求说明

在这里插入图片描述

查看响应说明

在这里插入图片描述

官方文档里都有介绍,见名知意的调用API

项目整合ElasticSearch完成商品上架
商品sku存储在es中的策略
  • 方案一

    • sku可检索的信息都存储在es的sku索引下
    • 方便检索,但是因为spu的某些规格参数相同,sku规格参数信息会发生冗余
  • 方案二

    • sku中与spu规格参数相同的信息存储在es的attr索引下
    • sku可检索的其他信息存储在es的sku索引下
    • 检索时查sku索引后还需要查询对应的attr索引
    • 不方便检索,信息不会冗余

时间,与空间不可兼得. 在并发量大的商城项目中应该追求尽快的响应,所以选择方案一,时间换空间

建立product索引映射规则

映射规则中 index:false,doc_value:false 表示该字段不参与全文检索,不需要维护它(它只是个"冗余字段",负责展示)

PUT product
{
  "mappings": {
    "properties": {
      "skuId": {
        "type": "long"
      },
      "spuId": {
        "type": "keyword"
      },
      "skuTitle": {
        "type": "text",
        "analyzer": "ik_smart"
      },
      "skuPrice": {
        "type": "keyword"
      },
      "skuImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "saleCount": {
        "type": "long"
      },
      "hasStock": {
        "type": "boolean"
      },
      "hotScore": {
        "type": "long"
      },
      "brandId": {
        "type": "long"
      },
      "catalogId": {
        "type": "long"
      },
      "brandName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "brandImg": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "catalogName": {
        "type": "keyword",
        "index": false,
        "doc_values": false
      },
      "attrs": {
        "type": "nested",
        "properties": {
          "attrId": {
            "type": "long"
          },
          "attrName": {
            "type": "keyword",
            "index": false,
            "doc_values": false
          },
          "attrValue": {
            "type": "keyword"
          }
        }
      }
    }
  }
}

attrs的type是nested 它可以避免对象的检索发生错误

数组的扁平化处理

在这里插入图片描述

当user的type 是 nested 嵌入式对象时 ,实际检索Alice Smith就不会检索到,避免发生检索错误

封装商品上架ES保存信息实体
@Data
public class SkuEsModel {
    /**
     * skuid
     */
    private Long skuId;
    /**
     * spuid
     */
    private Long spuId;
    /**
     * sku标题
     */
    private String skuTitle;
    /**
     * 价格
     */
    private BigDecimal skuPrice;
    /**
     * 封面图片
     */
    private String skuImg;
    /**
     * 销量
     */
    private Long saleCount;
    /**
     * 是否有库存
     */
    private boolean hasStock;
    /**
     * 热度
     */
    private Long hotScore;
    /**
     * 品牌id
     */
    private Long brandId;
    /**
     * 分类id
     */
    private Long catalogId;
    /**
     * 品牌名
     */
    private String brandName;
    /**
     * 品牌图片
     */
    private String brandImg;
    /**
     * 分类名
     */
    private String catalogName;
    /**
     * 属性(规格参数)集
     */
    private List<Attr> attrs;

    @Data
    public static class Attr{
        private Long attrId;
        private String attrName;
        private String attrValue;
    }
}
整合ElasticSearch

业务层封装好要上架的商品信息后,远程调用ES保存商品信息

核心代码 : ES保存商品信息

 	@Autowired
    RestHighLevelClient restHighLevelClient;

    /**
     * 上架商品 保存商品检索信息
     *
     * @return 是否上架失败
     */
    @Override
    public boolean productInfoSave(List<SkuEsModel> skuEsModels) throws IOException {
        BulkRequest bulkRequest = new BulkRequest();
        //1.批量添加要上架的商品
        for (SkuEsModel skuEsModel : skuEsModels) {
            //设置索引
            IndexRequest indexRequest = new IndexRequest(ESConstant.PRODUCT_INDEX);
            //设置skuId为文档ID
            indexRequest.id(skuEsModel.getSkuId().toString());
            //将上架商品信息转为JSON 并设置为source
            String jsonString = JSON.toJSONString(skuEsModel);
            indexRequest.source(jsonString, XContentType.JSON);

            bulkRequest.add(indexRequest);
        }


        //2.执行
        //BulkRequest bulkRequest, RequestOptions options
        BulkResponse response = restHighLevelClient.bulk(bulkRequest, GulimallElasticSearchConfig.COMMON_OPTIONS);


        //3.查看响应
        boolean failures = response.hasFailures();
        if (failures){
            List<String> list = Arrays.stream(response.getItems()).map(BulkItemResponse::getId).collect(Collectors.toList());
            log.error("商品上架失败:{}",list);
        }

        return failures;
    }
项目整合ES完成商品检索
关键主体代码
  1. 封装请求
  2. ES查询结果
  3. 分析结果封装响应
/**
 * 检索所有条件从ES中查询出结果
 * 规定:
 * 封装请求
 * 1.全文检索使用must (满足可提高评分)
 * 2.过滤(分类ID,品牌ID集合,属性对象集合,价格区间,库存)使用filter(与评分无关) 注意:对象要使用nested嵌入
 * 3.排序
 * 4.分页
 * 5.高亮
 * 封装响应
 * 1.聚合 (统计出信息,将可以检索的信息展示到页面)
 *
 * @param searchParamVo 检索条件
 * @return
 */
@Override
public SearchResultVo search(SearchParamVo searchParamVo) {
    SearchResultVo resultVo = null;

    //封装请求
    SearchRequest request = builderSearchRequest(searchParamVo);

    SearchResponse response = null;

    try {
        response = client.search(request, GulimallElasticSearchConfig.COMMON_OPTIONS);
    } catch (IOException e) {
        log.error("ES检索商品发生异常:", e);
    }

    //封装响应
    resultVo = builderSearchResponse(response, searchParamVo);

    return resultVo;
}
请求ES的DSL语句
GET gulimall_product/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "skuTitle": "华为"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "catalogId": "225"
          }
        },
        {
          "terms": {
            "brandId": [
              "2",
              "3"
            ]
          }
        },
        {
          "term": {
            "hasStock": false
          }
        },
        {
          "range": {
            "skuPrice": {
              "gte": 6000,
              "lte": 7500
            }
          }
        },
        {
          "nested": {
            "path": "attrs",
            "query": {
              "bool": {
                "must": [
                  {
                    "term": {
                      "attrs.attrId": {
                        "value": "2"
                      }
                    }
                  }
                ]
              }
            }
          }
        }
      ]
    }
  },
  "sort": [
    {
      "skuPrice": {
        "order": "desc"
      }
    }
  ],
  "from": 5,
  "size": 5,
  "highlight": {
    "fields": {"skuTitle" : {}},
    "pre_tags": "<b style='color:red'>",
    "post_tags": "</b>"
  },
  "aggs": {
    "brand_agg": {
      "terms": {
        "field": "brandId",
        "size": 10
      },
      "aggs": {
        "brand_name_agg": {
          "terms": {
            "field": "brandName",
            "size": 10
          }
        },
        "brand_img_agg": {
          "terms": {
            "field": "brandImg",
            "size": 10
          }
        }
      }
    },
    "catalog_agg": {
      "terms": {
        "field": "catalogId",
        "size": 10
      },
      "aggs": {
        "catalog_name_agg": {
          "terms": {
            "field": "catalogName",
            "size": 10
          }
        }
      }
    },
    "attrs": {
      "nested": {
        "path": "attrs"
      },
      "aggs": {
        "attrs_agg": {
          "terms": {
            "field": "attrs.attrId",
            "size": 10
          },
          "aggs": {
            "attrs_name_agg": {
              "terms": {
                "field": "attrs.attrName",
                "size": 10
              }
            },
            "attrs_value_agg": {
              "terms": {
                "field": "attrs.attrValue",
                "size": 10
              }
            }
          }
        }
      }
    }
  }
}
封装ES请求
	/**
     * 构造商品检索响应
     * 1.全文检索使用must (满足可提高评分)
     * 2.过滤(分类ID,品牌ID集合,属性对象集合,价格区间,库存)使用filter(与评分无关) 注意:对象要使用nested嵌入
     * 3.排序
     * 4.分页
     * 5.高亮
     * 6.聚合 (统计出信息,将可以检索的信息展示到页面)
     *
     * @param searchParamVo
     * @return
     */
    private SearchRequest builderSearchRequest(SearchParamVo searchParamVo) {

        //1.查询 : 全文检索 + 过滤
        SearchRequest request = new SearchRequest(ESConstant.PRODUCT_INDEX);
        SearchSourceBuilder builder = new SearchSourceBuilder();
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        //1.1 全文检索
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
            boolQueryBuilder.must(QueryBuilders.matchQuery("skuTitle", searchParamVo.getKeyword()));
        }

        //1.2 过滤分类
        if (searchParamVo.getCatalog3Id() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("catalogId", searchParamVo.getCatalog3Id()));
        }

        //1.3 过滤品牌id 因为品牌id可能是多个所以用terms
        if (searchParamVo.getBrandId() != null && searchParamVo.getBrandId().size() > 0) {
            boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", searchParamVo.getBrandId()));
        }

        //1.4 过滤库存
        if (searchParamVo.getHasStock() != null) {
            boolQueryBuilder.filter(QueryBuilders.termQuery("hasStock", searchParamVo.getHasStock()));
        }

        //1.5 过滤价格区间 skuPrice=400_2000  (400~2000) _100(100以下)  100_(100以上)
        if (!StringUtils.isEmpty(searchParamVo.getSkuPrice())) {
            RangeQueryBuilder rangeQueryBuilder = QueryBuilders.rangeQuery("skuPrice");

            String skuPrice = searchParamVo.getSkuPrice();
            String[] split = skuPrice.split("_");
            //100_的情况
            if (split.length == 1) {
                rangeQueryBuilder.gte(split[0]);
            } else {
                //长度为2时 2种情况: 400_2000    _100

                //_100的情况
//                if (StringUtils.isEmpty(split[0])){
//                    rangeQueryBuilder.lte(split[1]);
//                }else{
//                    //400_2000
//                    rangeQueryBuilder.gte(split[0]);
//                    rangeQueryBuilder.lte(split[1]);
//                }

                //化简
                if (!StringUtils.isEmpty(split[0])) {
                    rangeQueryBuilder.gte(split[0]);
                }
                rangeQueryBuilder.lte(split[1]);
            }
            boolQueryBuilder.filter(rangeQueryBuilder);
        }
        //1.6 过滤属性对象 (对象用嵌入) attrs=1_3G:4G:5G
        if (searchParamVo.getAttrs() != null && searchParamVo.getAttrs().size() > 0) {
            //可能要检索多属性
            List<String> attrs = searchParamVo.getAttrs();
            for (String attr : attrs) {
                //id:0 value:1
                String[] attrString = attr.split("_");
                String id = attrString[0];
                String[] value = attrString[1].split(":");

                BoolQueryBuilder nestedBool = QueryBuilders.boolQuery();
                //value 可能是多个要用terms
                nestedBool.must(QueryBuilders.termQuery("attrs.attrId", id));
                nestedBool.must(QueryBuilders.termsQuery("attrs.attrValue", value));

                NestedQueryBuilder nestedQueryBuilder = QueryBuilders.nestedQuery("attrs", nestedBool, ScoreMode.None);
                boolQueryBuilder.filter(nestedQueryBuilder);
            }
        }
        builder.query(boolQueryBuilder);

        //2.排序
        //sort=saleCount_asc/desc 销量
        //sort=hotScore_asc/desc 热度分
        //sort=skuPrice_asc/desc 价格
        if (!StringUtils.isEmpty(searchParamVo.getSort())) {
            String sort = searchParamVo.getSort();
            String[] sortSplit = sort.split("_");
            builder.sort(sortSplit[0], "asc".equalsIgnoreCase(sortSplit[1]) ? SortOrder.ASC : SortOrder.DESC);
        }

        //3.分页
        builder.from((searchParamVo.getPageNum() - 1) * ESConstant.PRODUCT_PAGE_SIZE);
        builder.size(ESConstant.PRODUCT_PAGE_SIZE);

        //4.高亮 有全文检索才高亮全文检索
        if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
            HighlightBuilder highlightBuilder = new HighlightBuilder();
            highlightBuilder.field("skuTitle");
            highlightBuilder.preTags("<b style='color:red'>");
            highlightBuilder.postTags("</b>");
            builder.highlighter(highlightBuilder);
        }


        //5.聚合
        //品牌聚合:id 子聚合:名字,图片
        TermsAggregationBuilder brandAgg = AggregationBuilders.terms("brand_agg").field("brandId");
        TermsAggregationBuilder brandName = AggregationBuilders.terms("brand_name_agg").field("brandName");
        TermsAggregationBuilder brandImg = AggregationBuilders.terms("brand_img_agg").field("brandImg");
        brandAgg.subAggregation(brandName);
        brandAgg.subAggregation(brandImg);
        builder.aggregation(brandAgg);

        //分类聚合:id 子聚合:名字
        TermsAggregationBuilder catalogAgg = AggregationBuilders.terms("catalog_agg").field("catalogId");
        TermsAggregationBuilder catalogName = AggregationBuilders.terms("catalog_name_agg").field("catalogName");
        catalogAgg.subAggregation(catalogName);
        builder.aggregation(catalogAgg);


        //属性聚合:嵌入式 id 子聚合:名字,值
        NestedAggregationBuilder nestedAttrsAgg = AggregationBuilders.nested("attrs", "attrs");

        TermsAggregationBuilder attrsAgg = AggregationBuilders.terms("attrs_agg").field("attrs.attrId");
        TermsAggregationBuilder attrsNameAgg = AggregationBuilders.terms("attrs_name_agg").field("attrs.attrName");
        TermsAggregationBuilder attrsValueAgg = AggregationBuilders.terms("attrs_value_agg").field("attrs.attrValue");
        attrsAgg.subAggregation(attrsNameAgg);
        attrsAgg.subAggregation(attrsValueAgg);

        nestedAttrsAgg.subAggregation(attrsAgg);
        builder.aggregation(nestedAttrsAgg);


        System.out.println(builder);
        request.source(builder);
        return request;
    }
分析结果封装响应
/**
     * 接受响应构造商品结果
     *
     * @param response
     * @param searchParamVo
     * @return
     */
    private SearchResultVo builderSearchResponse(SearchResponse response, SearchParamVo searchParamVo) {
        SearchResultVo resultVo = new SearchResultVo();
        SearchHits hits = response.getHits();

        //1.设置分页信息
        //当前页
        resultVo.setPageNum(searchParamVo.getPageNum());
        //总记录数
        TotalHits totalHits = hits.getTotalHits();
        long total = totalHits.value;
        resultVo.setTotal(total);
        //总页码  =  总记录数/每页大小 (向上取整)
        Integer totalPages = Math.toIntExact(total % ESConstant.PRODUCT_PAGE_SIZE == 0 ? total / ESConstant.PRODUCT_PAGE_SIZE : total / ESConstant.PRODUCT_PAGE_SIZE + 1);
        resultVo.setTotalPages(totalPages);
        //页码列表
        List<Integer> pageNavs = new ArrayList<>();
        for (Integer i = 1; i <= totalPages; i++) {
            pageNavs.add(i);
        }
        resultVo.setPageNavs(pageNavs);

        //2.设置查询到的所有商品信息
        List<SkuEsModel> skuEsModels = new ArrayList<>();
        for (SearchHit hit : hits.getHits()) {
            String source = hit.getSourceAsString();
            SkuEsModel skuEsModel = JSON.parseObject(source, SkuEsModel.class);
            //如果有全文检索要设置高亮
            if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
                HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
                Text[] fragments = skuTitle.getFragments();
                String highLightSkuTitle = fragments[0].string();
                skuEsModel.setSkuTitle(highLightSkuTitle);
            }
            skuEsModels.add(skuEsModel);
        }
        resultVo.setProducts(skuEsModels);


        Aggregations aggregations = response.getAggregations();
        //3.设置查询涉及所有品牌
        List<SearchResultVo.BrandVo> brandVos = new ArrayList<>();
        ParsedLongTerms brandAgg = aggregations.get("brand_agg");
        for (Terms.Bucket bucket : brandAgg.getBuckets()) {
            SearchResultVo.BrandVo brandVo = new SearchResultVo.BrandVo();
            //品牌id
            long brandId = bucket.getKeyAsNumber().longValue();

            //品牌名 一个id对应一个品牌名 所以从列表下标为0拿元素即可
            ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
            String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();

            //品牌图片 一个id对应一个品牌图片 所以从列表下标为0拿元素即可
            ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
            String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();

            brandVo.setBrandId(brandId);
            brandVo.setBrandName(brandName);
            brandVo.setBrandImg(brandImg);
            brandVos.add(brandVo);
        }
        resultVo.setBrands(brandVos);

        //4.设置查询涉及所有分类
        List<SearchResultVo.CatalogVo> catalogVos = new ArrayList<>();
        ParsedLongTerms catalogAgg = aggregations.get("catalog_agg");
        for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
            SearchResultVo.CatalogVo catalogVo = new SearchResultVo.CatalogVo();
            //分类ID
            long catalogId = bucket.getKeyAsNumber().longValue();
            //分类名  一个分类ID对应一个分类名
            ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
            String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();

            catalogVo.setCatalogId(catalogId);
            catalogVo.setCatalogName(catalogName);
            catalogVos.add(catalogVo);
        }
        resultVo.setCatalogs(catalogVos);


        //5.设置查询涉及所有属性
        List<SearchResultVo.AttrVo> attrVos = new ArrayList<>();
        ParsedNested attrs = aggregations.get("attrs");
        ParsedLongTerms attrsAgg = attrs.getAggregations().get("attrs_agg");
        for (Terms.Bucket bucket : attrsAgg.getBuckets()) {
            SearchResultVo.AttrVo attrVo = new SearchResultVo.AttrVo();
            //属性ID 一个属性ID对应一个属性名对应多个属性值
            long attrId = bucket.getKeyAsNumber().longValue();
            //属性名
            ParsedStringTerms attrsNameAgg = bucket.getAggregations().get("attrs_name_agg");
            String attrName = attrsNameAgg.getBuckets().get(0).getKeyAsString();
            //属性值
            ParsedStringTerms attrsValueAgg = bucket.getAggregations().get("attrs_value_agg");
            List<String> attrValue = attrsValueAgg.getBuckets().stream().map(item -> item.getKeyAsString()).collect(Collectors.toList());

            attrVo.setAttrId(attrId);
            attrVo.setAttrName(attrName);
            attrVo.setAttrValue(attrValue);
            attrVos.add(attrVo);
        }
        resultVo.setAttrs(attrVos);

        return resultVo;
    }
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值