项目结构
SearchController
package com.atguigu.gmall.search.controller;
import com.atguigu.gmall.search.pojo.SearchParamVo;
import com.atguigu.gmall.search.pojo.SearchResponseVo;
import com.atguigu.gmall.search.service.SearchService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
@Controller
public class SearchController {
@Autowired
private SearchService searchService;
@GetMapping("search")
public String search(SearchParamVo paramVo, Model model){
SearchResponseVo responseVo = this.searchService.search(paramVo);
model.addAttribute("response", responseVo);
model.addAttribute("searchParam", paramVo);
return "search";
}
}
pojo
Goods
package com.atguigu.gmall.search.pojo;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
import java.math.BigDecimal;
import java.util.Date;
import java.util.List;
@Document(indexName = "goods", shards = 3, replicas = 2)
@Data
public class Goods {
@Id
private Long skuId;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title;
@Field(type = FieldType.Keyword, index = false)
private String subTitle;
@Field(type = FieldType.Double)
private BigDecimal price;
@Field(type = FieldType.Keyword, index = false)
private String defaultImage;
@Field(type = FieldType.Long)
private Long sales = 0l;
@Field(type = FieldType.Date, format = DateFormat.date_time)
private Date createTime;
@Field(type = FieldType.Boolean)
private Boolean store = false;
@Field(type = FieldType.Long)
private Long brandId;
@Field(type = FieldType.Keyword)
private String brandName;
@Field(type = FieldType.Keyword)
private String logo;
@Field(type = FieldType.Long)
private Long categoryId;
@Field(type = FieldType.Keyword)
private String categoryName;
@Field(type = FieldType.Nested)
private List<SearchAttrValueVo> searchAttrs;
}
SearchAttrValueVo
package com.atguigu.gmall.search.pojo;
import lombok.Data;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Data
public class SearchAttrValueVo {
@Field(type= FieldType.Long)
private Long attrId;
@Field(type= FieldType.Keyword)
private String attrName;
@Field(type= FieldType.Keyword)
private String attrValue;
}
SearchParamVo
package com.atguigu.gmall.search.pojo;
import lombok.Data;
import java.util.List;
@Data
public class SearchParamVo {
private String keyword;
private List<Long> brandId;
private List<Long> categoryId;
private List<String> props;
private Double priceFrom;
private Double priceTo;
private Boolean store;
private Integer sort = 0;
private Integer pageNum=1;
private final Integer pageSize=20;
}
SearchResponseAttrVo
package com.atguigu.gmall.search.pojo;
import lombok.Data;
import java.util.List;
@Data
public class SearchResponseAttrVo {
private Long attrId;
private String attrName;
private List<String> attrValues;
}
SearchResponseVo
package com.atguigu.gmall.search.pojo;
import com.atguigu.gmall.pms.entity.BrandEntity;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import lombok.Data;
import java.util.List;
@Data
public class SearchResponseVo {
private List<BrandEntity> brands;
private List<CategoryEntity> categories;
private List<SearchResponseAttrVo> filters;
private Integer pageNum;
private Integer pageSize;
private Long total;
private List<Goods> goodsList;
}
SearchService
package com.atguigu.gmall.search.service;
import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.pms.entity.BrandEntity;
import com.atguigu.gmall.pms.entity.CategoryEntity;
import com.atguigu.gmall.search.pojo.Goods;
import com.atguigu.gmall.search.pojo.SearchParamVo;
import com.atguigu.gmall.search.pojo.SearchResponseAttrVo;
import com.atguigu.gmall.search.pojo.SearchResponseVo;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang3.math.NumberUtils;
import org.apache.lucene.search.join.ScoreMode;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.SearchHits;
import org.elasticsearch.search.aggregations.AggregationBuilders;
import org.elasticsearch.search.aggregations.Aggregations;
import org.elasticsearch.search.aggregations.bucket.nested.ParsedNested;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedLongTerms;
import org.elasticsearch.search.aggregations.bucket.terms.ParsedStringTerms;
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
@Service
public class SearchService {
@Autowired
private RestHighLevelClient restHighLevelClient;
public SearchResponseVo search(SearchParamVo paramVo) {
try {
SearchResponse response = this.restHighLevelClient.search(new SearchRequest(new String[]{"goods"}, buildDsl(paramVo)), RequestOptions.DEFAULT);
SearchResponseVo responseVo = this.parseResult(response);
responseVo.setPageNum(paramVo.getPageNum());
responseVo.setPageSize(paramVo.getPageSize());
return responseVo;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
private SearchResponseVo parseResult(SearchResponse response) {
SearchResponseVo responseVo = new SearchResponseVo();
SearchHits hits = response.getHits();
responseVo.setTotal(hits.getTotalHits().value);
SearchHit[] hitsHits = hits.getHits();
if (hitsHits != null || hitsHits.length > 0) {
List<Goods> goodsList = Stream.of(hitsHits).map(hitsHit -> {
String json = hitsHit.getSourceAsString();
Goods goods = JSON.parseObject(json, Goods.class);
Map<String, HighlightField> highlightFields = hitsHit.getHighlightFields();
HighlightField highlightField = highlightFields.get("title");
String highlightTitle = highlightField.fragments()[0].string();
goods.setTitle(highlightTitle);
return goods;
}).collect(Collectors.toList());
responseVo.setGoodsList(goodsList);
}
Aggregations aggregations = response.getAggregations();
ParsedLongTerms brandIdAgg = aggregations.get("brandIdAgg");
List<? extends Terms.Bucket> brandIdAggBuckets = brandIdAgg.getBuckets();
if (!CollectionUtils.isEmpty(brandIdAggBuckets)) {
List<BrandEntity> brands = brandIdAggBuckets.stream().map(bucket -> {
BrandEntity brandEntity = new BrandEntity();
brandEntity.setId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
Aggregations subAggs = ((Terms.Bucket) bucket).getAggregations();
ParsedStringTerms brandNameAgg = subAggs.get("brandNameAgg");
List<? extends Terms.Bucket> nameAggBuckets = brandNameAgg.getBuckets();
if (!CollectionUtils.isEmpty(nameAggBuckets)) {
brandEntity.setName(nameAggBuckets.get(0).getKeyAsString());
}
ParsedStringTerms logoAgg = subAggs.get("logoAgg");
List<? extends Terms.Bucket> logoAggBuckets = logoAgg.getBuckets();
if (!CollectionUtils.isEmpty(logoAggBuckets)) {
brandEntity.setLogo(logoAggBuckets.get(0).getKeyAsString());
}
return brandEntity;
}).collect(Collectors.toList());
responseVo.setBrands(brands);
}
ParsedLongTerms categoryIdAgg = aggregations.get("categoryIdAgg");
List<? extends Terms.Bucket> categoryIdAggBuckets = categoryIdAgg.getBuckets();
if (!CollectionUtils.isEmpty(categoryIdAggBuckets)) {
List<CategoryEntity> categories = categoryIdAggBuckets.stream().map(bucket -> {
CategoryEntity categoryEntity = new CategoryEntity();
categoryEntity.setId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
ParsedStringTerms categoryNameAgg = ((Terms.Bucket) bucket).getAggregations().get("categoryNameAgg");
List<? extends Terms.Bucket> buckets = categoryNameAgg.getBuckets();
if (!CollectionUtils.isEmpty(buckets)) {
categoryEntity.setName(buckets.get(0).getKeyAsString());
}
return categoryEntity;
}).collect(Collectors.toList());
responseVo.setCategories(categories);
}
ParsedNested attrAgg = aggregations.get("attrAgg");
ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
List<? extends Terms.Bucket> buckets = attrIdAgg.getBuckets();
if (!CollectionUtils.isEmpty(buckets)) {
List<SearchResponseAttrVo> filters = buckets.stream().map(bucket -> {
SearchResponseAttrVo searchResponseAttrVo = new SearchResponseAttrVo();
searchResponseAttrVo.setAttrId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
Aggregations subAggs = ((Terms.Bucket) bucket).getAggregations();
ParsedStringTerms attrNameAgg = subAggs.get("attrNameAgg");
List<? extends Terms.Bucket> nameAggBuckets = attrNameAgg.getBuckets();
if (!CollectionUtils.isEmpty(nameAggBuckets)) {
searchResponseAttrVo.setAttrName(nameAggBuckets.get(0).getKeyAsString());
}
ParsedStringTerms attrValueAgg = subAggs.get("attrValueAgg");
List<? extends Terms.Bucket> valueAggBuckets = attrValueAgg.getBuckets();
if (!CollectionUtils.isEmpty(valueAggBuckets)) {
List<String> attrValues = valueAggBuckets.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
searchResponseAttrVo.setAttrValues(attrValues);
}
return searchResponseAttrVo;
}).collect(Collectors.toList());
responseVo.setFilters(filters);
}
return responseVo;
}
private SearchSourceBuilder buildDsl(SearchParamVo paramVo) {
String keyword = paramVo.getKeyword();
if (StringUtils.isBlank(keyword)) {
throw new RuntimeException("搜索条件不能为空");
}
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
sourceBuilder.query(boolQueryBuilder);
boolQueryBuilder.must(QueryBuilders.matchQuery("title", keyword).operator(Operator.AND));
List<Long> brandId = paramVo.getBrandId();
if (!CollectionUtils.isEmpty(brandId)) {
boolQueryBuilder.filter(QueryBuilders.termsQuery("brandId", brandId));
}
List<Long> categoryId = paramVo.getCategoryId();
if (!CollectionUtils.isEmpty(categoryId)) {
boolQueryBuilder.filter(QueryBuilders.termsQuery("categoryId", categoryId));
}
Double priceFrom = paramVo.getPriceFrom();
Double priceTo = paramVo.getPriceTo();
if (priceFrom != null || priceTo != null) {
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("price");
boolQueryBuilder.filter(rangeQuery);
if (priceFrom != null) {
rangeQuery.gte(priceFrom);
}
if (priceTo != null) {
rangeQuery.lte(priceTo);
}
}
Boolean store = paramVo.getStore();
if (store != null) {
boolQueryBuilder.filter(QueryBuilders.termQuery("store", store));
}
List<String> props = paramVo.getProps();
if (!CollectionUtils.isEmpty(props)) {
props.forEach(prop -> {
String[] attrs = StringUtils.split(prop, ":");
if (attrs != null && attrs.length == 2 && NumberUtils.isCreatable(attrs[0])) {
String attrId = attrs[0];
String attrValueString = attrs[1];
String[] attrValues = StringUtils.split(attrValueString, "-");
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.termQuery("searchAttrs.attrId", attrId));
boolQuery.must(QueryBuilders.termsQuery("searchAttrs.attrValue", attrValues));
NestedQueryBuilder searchAttrs = QueryBuilders.nestedQuery("searchAttrs", boolQuery, ScoreMode.None);
boolQueryBuilder.filter(searchAttrs);
}
});
}
Integer sort = paramVo.getSort();
switch (sort) {
case 1:
sourceBuilder.sort("price", SortOrder.DESC);
break;
case 2:
sourceBuilder.sort("price", SortOrder.ASC);
break;
case 3:
sourceBuilder.sort("sales", SortOrder.DESC);
break;
case 4:
sourceBuilder.sort("createTime", SortOrder.DESC);
break;
default:
sourceBuilder.sort("_score", SortOrder.DESC);
break;
}
Integer pageNum = paramVo.getPageNum();
Integer pageSize = paramVo.getPageSize();
sourceBuilder.from((pageNum - 1) * pageSize);
sourceBuilder.size(pageSize);
sourceBuilder.highlighter(new HighlightBuilder().field("title")
.preTags("<font style='color:red;'>")
.postTags("</font>"));
sourceBuilder.aggregation(AggregationBuilders.terms("brandIdAgg").field("brandId")
.subAggregation(AggregationBuilders.terms("brandNameAgg").field("brandName"))
.subAggregation(AggregationBuilders.terms("logoAgg").field("logo")));
sourceBuilder.aggregation(AggregationBuilders.terms("categoryIdAgg").field("categoryId")
.subAggregation(AggregationBuilders.terms("categoryNameAgg").field("categoryName")));
sourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "searchAttrs")
.subAggregation(AggregationBuilders.terms("attrIdAgg").field("searchAttrs.attrId")
.subAggregation(AggregationBuilders.terms("attrNameAgg").field("searchAttrs.attrName"))
.subAggregation(AggregationBuilders.terms("attrValueAgg").field("searchAttrs.attrValue"))));
sourceBuilder.fetchSource(new String[]{"skuId", "title", "subTitle", "price", "defaultImage"}, null);
System.out.println(sourceBuilder);
return sourceBuilder;
}
}
GmallSearchApplication
package com.atguigu.gmall.search;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class GmallSearchApplication {
public static void main(String[] args) {
SpringApplication.run(GmallSearchApplication.class, args);
}
}