文章目录
项目整合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完成商品检索
关键主体代码
- 封装请求
- ES查询结果
- 分析结果封装响应
/**
* 检索所有条件从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;
}