一、controller层
@RestController
@RequestMapping("/api/inner/rpc/search")
public class SearchRpcController {
@Resource
SearchService searchService;
@PostMapping("/searchgoods")
public Result<SearchRespVo> search(@RequestBody SearchParamVo searchParamVo){
SearchRespVo searchRespVo=searchService.search(searchParamVo);
return Result.ok(searchRespVo);
}
@GetMapping("/down/goods/{skuId}")
public Result down(@PathVariable Long skuId){
searchService.down(skuId);
return Result.ok();
}
@PostMapping("up/goods")
public Result up(@RequestBody Goods goods){
searchService.up(goods);
return Result.ok();
}
@GetMapping("/hotscore/{skuId}/{score}")
public Result updateHotScore(@PathVariable Long skuId,@PathVariable Long score){
searchService.updateHotScore(skuId,score);
return Result.ok();
}
二、service层
public interface SearchService {
void down(Long skuId);
void up(Goods goods);
SearchRespVo search(SearchParamVo searchParamVo);
void updateHotScore(Long skuId, Long score);
}
三、service的实现层
@Service
public class SearchServiceImpl implements SearchService {
@Resource
GoodsRepositories goodsRepositories;
@Resource
ElasticsearchRestTemplate restTemplate;
int pageSize=10;
@Override
public void down(Long skuId) {
goodsRepositories.deleteById(skuId);
}
@Override
public void up(Goods goods) {
goodsRepositories.save(goods);
}
@Override
public SearchRespVo search(SearchParamVo searchParamVo) {
Query query = getQuery(searchParamVo);
SearchHits<Goods> goods = restTemplate.search(query,
Goods.class,
IndexCoordinates.of("goods"));
SearchRespVo respVo=builderSearchResp(goods,searchParamVo);
return respVo;
}
@Override
public void updateHotScore(Long skuId, Long score) {
Document document = Document.create();
document.put("hotScore",score);
UpdateQuery query = UpdateQuery.builder(skuId + "")
.withDocAsUpsert(true)
.withDocument(document).build();
restTemplate.update(query,IndexCoordinates.of("goods"));
}
private SearchRespVo builderSearchResp(SearchHits<Goods> goods,SearchParamVo searchParamVo) {
SearchRespVo respVo = new SearchRespVo();
respVo.setSearchParamVo(searchParamVo);
if (!StringUtils.isEmpty(searchParamVo.getTrademark())){
respVo.setTrademarkParam("品牌: "+searchParamVo.getTrademark().split(":")[1]);
}
if (searchParamVo.getProps()!=null && searchParamVo.getProps().length>0) {
List<SearchRespVo.Props> propsList = Arrays.stream(searchParamVo.getProps())
.map(item -> {
String[] split = item.split(":");
SearchRespVo.Props props = new SearchRespVo.Props();
props.setAttrName(split[2]);
props.setAttrValue(split[1]);
props.setAttrId(Long.parseLong(split[0]));
return props;
}).collect(Collectors.toList());
respVo.setPropsParamList(propsList);
}
ParsedLongTerms tmIdAgg = goods.getAggregations().get("tmIdAgg");
List<SearchRespVo.Trademark> trademarks = tmIdAgg.getBuckets().stream()
.map(bucket -> {
SearchRespVo.Trademark trademark = new SearchRespVo.Trademark();
trademark.setTmId(bucket.getKeyAsNumber().longValue());
ParsedStringTerms tmNameAgg = bucket.getAggregations().get("tmNameAgg");
String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
trademark.setTmName(tmName);
ParsedStringTerms tmLogoAgg = bucket.getAggregations().get("tmLogoAgg");
String tmLogoUrl = tmLogoAgg.getBuckets().get(0).getKeyAsString();
trademark.setTmLogoUrl(tmLogoUrl);
return trademark;
}).collect(Collectors.toList());
respVo.setTrademarkList(trademarks);
ParsedNested attrsAgg = goods.getAggregations().get("attrsAgg");
ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attrIdAgg");
List<SearchRespVo.Arrts> arrtsList = attrIdAgg.getBuckets().stream().map(bucket -> {
SearchRespVo.Arrts arrts = new SearchRespVo.Arrts();
long attrId = bucket.getKeyAsNumber().longValue();
arrts.setAttrId(attrId);
ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
arrts.setAttrName(attrName);
ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attrValueAgg");
List<String> stringList = attrValueAgg.getBuckets().stream()
.map(item -> item.getKeyAsString()).collect(Collectors.toList());
arrts.setAttrValueList(stringList);
return arrts;
}).collect(Collectors.toList());
respVo.setAttrsList(arrtsList);
String urlParam = builderUrlParam(searchParamVo);
respVo.setUrlParam(urlParam);
if (!StringUtils.isEmpty(searchParamVo.getOrder())){
OrderMap orderMap = new OrderMap();
String[] split = searchParamVo.getOrder().split(":");
orderMap.setSort(split[1]);
orderMap.setType(split[0]);
respVo.setOrderMap(orderMap);
}
List<Goods> goodsList = goods.getSearchHits().stream()
.map(good -> {
Goods content = good.getContent();
if(!StringUtils.isEmpty(searchParamVo.getKeyword())){
String newTitle = good.getHighlightField("title").get(0);
content.setTitle(newTitle);
}
return content;
})
.collect(Collectors.toList());
respVo.setGoodsList(goodsList);
respVo.setPageNo(searchParamVo.getPageNo());
long totalHits = goods.getTotalHits();
respVo.setTotalPages(totalHits % pageSize == 0 ? totalHits / pageSize : totalHits / pageSize + 1);
return respVo;
}
private String builderUrlParam(SearchParamVo searchParamVo) {
StringBuilder builder=new StringBuilder("list.html?");
if(searchParamVo.getCategory1Id()!=null){
builder.append("&category1Id="+searchParamVo.getCategory1Id());
}
if(searchParamVo.getCategory2Id()!=null){
builder.append("&category2Id="+searchParamVo.getCategory2Id());
}
if(searchParamVo.getCategory3Id()!=null){
builder.append("&category3Id="+searchParamVo.getCategory3Id());
}
if (!StringUtils.isEmpty(searchParamVo.getKeyword())){
builder.append("&keyword="+searchParamVo.getKeyword());
}
if (!StringUtils.isEmpty(searchParamVo.getTrademark())){
builder.append("&trademark="+searchParamVo.getTrademark());
}
if(searchParamVo.getProps()!=null && searchParamVo.getProps().length>0){
Arrays.stream(searchParamVo.getProps()).forEach(item->{
builder.append("&props="+item);
});
}
return builder.toString();
}
private Query getQuery(SearchParamVo searchParamVo) {
BoolQueryBuilder bool = QueryBuilders.boolQuery();
if (searchParamVo.getCategory1Id() != null) {
bool.must(QueryBuilders.termQuery("category1Id", searchParamVo.getCategory1Id()));
}
if (searchParamVo.getCategory2Id() != null) {
bool.must(QueryBuilders.termQuery("category2Id", searchParamVo.getCategory2Id()));
}
if (searchParamVo.getCategory3Id() != null) {
bool.must(QueryBuilders.termQuery("category3Id", searchParamVo.getCategory3Id()));
}
if (!StringUtils.isEmpty(searchParamVo.getKeyword())) {
bool.must(QueryBuilders.matchQuery("title", searchParamVo.getKeyword()));
}
if (!StringUtils.isEmpty(searchParamVo.getTrademark())) {
String[] split = searchParamVo.getTrademark().split(":");
bool.must(QueryBuilders.termQuery("tmId", split[0]));
}
if (searchParamVo.getProps() != null && searchParamVo.getProps().length > 0) {
Arrays.stream(searchParamVo.getProps())
.forEach(item->{
String[] split = item.split(":");
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
boolQuery.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
boolQuery.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
NestedQueryBuilder nested = QueryBuilders.nestedQuery("attrs",boolQuery, ScoreMode.None);
bool.must(nested);
});
}
NativeSearchQuery query = new NativeSearchQuery(bool);
if (!StringUtils.isEmpty(searchParamVo.getOrder())) {
Sort sort = null;
String[] split = searchParamVo.getOrder().split(":");
Sort.Direction direction= "asc".equalsIgnoreCase(split[1])?Sort.Direction.ASC:Sort.Direction.DESC;
switch (split[0]){
case "1":
sort=Sort.by(direction, "hotScore");
break;
case "2":
sort=Sort.by(direction, "price");
break;
default:
sort=Sort.by(Sort.Direction.DESC, "hotScore");
}
query.addSort(sort);
}
Pageable pageable=PageRequest.of(searchParamVo.getPageNo()-1,pageSize);
query.setPageable(pageable);
TermsAggregationBuilder tmIdAgg = AggregationBuilders.terms("tmIdAgg")
.field("tmId")
.size(200);
tmIdAgg.subAggregation(AggregationBuilders.terms("tmNameAgg")
.field("tmName")
.size(1));
tmIdAgg.subAggregation(AggregationBuilders.terms("tmLogoAgg")
.field("tmLogoUrl")
.size(1));
query.addAggregation(tmIdAgg);
NestedAggregationBuilder attrsAgg = AggregationBuilders.nested("attrsAgg", "attrs");
TermsAggregationBuilder attrIdAgg = AggregationBuilders.terms("attrIdAgg")
.field("attrs.attrId")
.size(200);
attrIdAgg.subAggregation(AggregationBuilders.terms("attrNameAgg")
.field("attrs.attrName")
.size(1));
attrIdAgg.subAggregation(AggregationBuilders.terms("attrValueAgg")
.field("attrs.attrValue")
.size(200));
attrsAgg.subAggregation(attrIdAgg);
query.addAggregation(attrsAgg);
if(!StringUtils.isEmpty(searchParamVo.getKeyword())){
HighlightBuilder builder = new HighlightBuilder();
builder.field("title")
.preTags("")
.postTags("");
HighlightQuery highlightQuery = new HighlightQuery(builder);
query.setHighlightQuery(highlightQuery);
}
return query;
}
}
四、对应的es的DLS语句
# 注dls语句只有部分语句,具体请参考service的实现代码
GET goods/_search
{
"query": {
"bool": {
"must": [
{
"term": {
"category3Id": {
"value": "61"
}
}
},
{
"match": {
"title": "小米"
}
}
]
}
},
"from": 0,
"size": 10,
"sort": [
{
"price": {
"order": "asc"
}
}
],
"aggs": {
"tmIdAgg": {
"terms": {
"field": "tmId",
"size": 200
},
"aggs": {
"tmNameAgg": {
"terms": {
"field": "tmName",
"size": 1
}
},
"tmValueAgg":{
"terms": {
"field": "tmLogoUrl",
"size": 1
}
}
}
},
"attrsAgg":{
"nested": {
"path": "attrs"
},
"aggs": {
"attrIdAgg": {
"terms": {
"field": "attrs.attrId",
"size": 200
},
"aggs": {
"attrNameAgg": {
"terms": {
"field": "attrs.attrName",
"size": 1
}
},
"attrValueAgg":{
"terms": {
"field": "attrs.attrValue",
"size": 200
}
}
}
}
}
}
},
"highlight": {
"fields": {
"title": {
"pre_tags": "",
"post_tags": ""
}
}
}
}
五、GoodsRepositories类
@Repository
public interface GoodsRepositories extends PagingAndSortingRepository<Goods,Long> {
}
六 ,对应的实体类
Goods实体类
@Data
@Document(indexName = "goods" , shards = 3,replicas = 2)
public class Goods {
@Id
private Long id;
@Field(type = FieldType.Keyword, index = false)
private String defaultImg;
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title;
@Field(type = FieldType.Double)
private Double price;
@Field(type = FieldType.Date,format = DateFormat.custom,pattern = "yyyy-MM-dd HH:mm:ss")
private Date createTime;
@Field(type = FieldType.Long)
private Long tmId;
@Field(type = FieldType.Keyword)
private String tmName;
@Field(type = FieldType.Keyword)
private String tmLogoUrl;
@Field(type = FieldType.Long)
private Long category1Id;
@Field(type = FieldType.Keyword)
private String category1Name;
@Field(type = FieldType.Long)
private Long category2Id;
@Field(type = FieldType.Keyword)
private String category2Name;
@Field(type = FieldType.Long)
private Long category3Id;
@Field(type = FieldType.Keyword)
private String category3Name;
@Field(type = FieldType.Long)
private Long hotScore = 0L;
@Field(type = FieldType.Nested)
private List<SearchAttr> attrs;
}
SearchAttr实体类
@Data
public class SearchAttr {
@Field(type = FieldType.Long)
private Long attrId;
@Field(type = FieldType.Keyword)
private String attrValue;
@Field(type = FieldType.Keyword)
private String attrName;
}
SearchParamVo
@Data
public class SearchParamVo {
Long category1Id;
Long category2Id;
Long category3Id;
String keyword;
String trademark;
String[] props;
String order="1:desc";
Integer pageNo=1;
}
SearchRespVo
@Data
public class SearchRespVo {
private SearchParamVo searchParamVo;
private String trademarkParam;
private List<Props> propsParamList;
private List<Trademark> trademarkList;
private List<Arrts> attrsList;
private String urlParam;
private OrderMap orderMap;
private List<Goods> goodsList;
private Integer pageNo;
private Long totalPages;
@Data
public static class Props {
private String attrName;
private String attrValue;
private Long attrId;
}
@Data
public static class Trademark {
private Long tmId;
private String tmName;
private String tmLogoUrl;
}
@Data
public static class Arrts {
private String attrName;
private Long attrId;
private List<String> attrValueList;
}
@Data
public static class OrderMap {
private String type;
private String sort;
}
}