目录:
(1)封装搜索相关实体对象
(2)搜索接口封装
(3)在service-list-client模块添加远程接口
(1)封装搜索相关实体对象
搜索参数实体:SearchParam
搜索参数实体:SearchParam
package com.atguigu.gmall.model.list;
/**
* 商品搜索参数
* 参数说明:
* 1,商标品牌:trademark=2:华为
* 2:为品牌id,搜索字段
* 华为:品牌名称,页面回显属性
* 2,平台属性:props=23:4G:运行内存
* 23:平台属性id,搜索字段
* 运行内存:平台属性名称,页面回显属性
* 4G:平台属性值,搜索字段与页面回显属性
* </p>
*
*/
@Data
public class SearchParam {
// ?category3Id=61&trademark=2:华为&props=23:4G:运行内存&order=1:desc
//category3Id=61
private Long category1Id;;//三级分类id
private Long category2Id;
private Long category3Id;
//trademark=2:华为
private String trademark;//品牌id
private String keyword;//检索的关键字
// order=1:asc 排序规则 0:asc
private String order = "";// 1:综合排序/热点 2:价格
//props=23:4G:运行内存
private String[] props;//页面提交的数组
private Integer pageNo = 1;//分页信息
private Integer pageSize = 12;
}
搜索结果集实体:SearchResponseVo
搜索结果集实体:SearchResponseVo
package com.atguigu.gmall.model.list;
@Data
public class SearchResponseVo implements Serializable {
//品牌 此时vo对象中的id字段保留(不用写) name就是“品牌” value: [{id:100,name:华为,logo:xxx},{id:101,name:小米,log:yyy}]
private List<SearchResponseTmVo> trademarkList;
//所有商品的顶头显示的筛选属性
private List<SearchResponseAttrVo> attrsList = new ArrayList<>();
//检索出来的商品信息
private List<Goods> goodsList = new ArrayList<>();
private Long total;//总记录数
private Integer pageSize;//每页显示的内容
private Integer pageNo;//当前页面
private Long totalPages;
}
结果集品牌实体:SearchResponseTmVo
package com.atguigu.gmall.model.list;
@Data
public class SearchResponseTmVo implements Serializable {
//当前属性值的所有值
private Long tmId;
//属性名称
private String tmName;//网络制式,分类
//图片url
private String tmLogoUrl;
}
结果集平台属性实体:SearchResponseAttrVo
package com.atguigu.gmall.model.list;
@Data
public class SearchResponseAttrVo implements Serializable {
private Long attrId;//1
//当前属性值的所有值
private List<String> attrValueList = new ArrayList<>();
//属性名称
private String attrName;//网络制式,分类
}
(2)搜索接口封装
SearchService接口
/**
* 搜索列表
* @param searchParam
* @return
* @throws IOException
*/
SearchResponseVo search(SearchParam searchParam) throws IOException;
接口实现类
api参考文档:
Java REST Client [7.8] | Elastic
Java REST Client [7.8] | Elastic
@Autowired
private RestHighLevelClient restHighLevelClient;
@Override
public SearchResponseVo search(SearchParam searchParam) throws IOException {
// 构建dsl语句
SearchRequest searchRequest = this.buildQueryDsl(searchParam);
SearchResponse response = this.restHighLevelClient.search(searchRequest, RequestOptions.DEFAULT);
System.out.println(response);
SearchResponseVo responseVO = this.parseSearchResult(response);
responseVO.setPageSize(searchParam.getPageSize());
responseVO.setPageNo(searchParam.getPageNo());
long totalPages = (responseVO.getTotal()+searchParam.getPageSize()-1)/searchParam.getPageSize();
responseVO.setTotalPages(totalPages);
return responseVO;
}
//封装查询条件
// 制作dsl 语句
private SearchRequest buildQueryDsl(SearchParam searchParam) {
// 构建查询器
SearchSourceBuilder searchSourceBuilder = new SearchSourceBuilder();
// 构建多条件对象boolQueryBuilder
BoolQueryBuilder boolQueryBuilder = QueryBuilders.boolQuery();
// 判断查询条件是否为空 关键字
if (!StringUtils.isEmpty(searchParam.getKeyword())){
// 小米手机 小米and手机
// MatchQueryBuilder matchQueryBuilder = new MatchQueryBuilder("title",searchParam.getKeyword()).operator(Operator.AND);
MatchQueryBuilder title = QueryBuilders.matchQuery("title", searchParam.getKeyword()).operator(Operator.AND);
boolQueryBuilder.must(title);
}
// 构建品牌查询
String trademark = searchParam.getTrademark();
if (!StringUtils.isEmpty(trademark)){
// trademark=2:华为
String[] split = StringUtils.split(trademark, ":");
if (split != null && split.length == 2) {
//构建过滤品牌
TermQueryBuilder tmId=QueryBuilders.termQuery("tmId", split[0]);
// 根据品牌Id过滤 添加到多条件对象
boolQueryBuilder.filter(tmId);
}
}
// 构建分类过滤 用户在点击的时候,只能点击一个值,所以此处使用term
if(null!=searchParam.getCategory1Id()){
boolQueryBuilder.filter(QueryBuilders.termQuery("category1Id",searchParam.getCategory1Id()));
}
// 构建分类过滤
if(null!=searchParam.getCategory2Id()){
boolQueryBuilder.filter(QueryBuilders.termQuery("category2Id",searchParam.getCategory2Id()));
}
// 构建分类过滤
if(null!=searchParam.getCategory3Id()){
boolQueryBuilder.filter(QueryBuilders.termQuery("category3Id",searchParam.getCategory3Id()));
}
// 构建平台属性查询
// 23:4G:运行内存
String[] props = searchParam.getProps();
if (props!=null && props.length>0){
// 循环遍历
for (String prop : props) {
// 23:4G:运行内存 平台属性id:平台属性值名称:平台属性名
String[] split = StringUtils.split(prop, ":");
if (split!=null && split.length==3){
// 构建嵌套查询 创建多条件对象
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
// 嵌套查询子查询
BoolQueryBuilder subBoolQuery = QueryBuilders.boolQuery();
// 构建子查==询中的过滤条件
subBoolQuery.must(QueryBuilders.termQuery("attrs.attrId",split[0]));
subBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue",split[1]));
// ScoreMode.None ?
boolQuery.must(QueryBuilders.nestedQuery("attrs",subBoolQuery, ScoreMode.None));
// 添加到整个过滤对象中,外层对象
boolQueryBuilder.filter(boolQuery);
}
}
}
// 执行查询方法
searchSourceBuilder.query(boolQueryBuilder);
// 构建分页
int from = (searchParam.getPageNo()-1)*searchParam.getPageSize();
searchSourceBuilder.from(from);
searchSourceBuilder.size(searchParam.getPageSize());
// 排序 1:hotScore 2:price 1:综合排序/热度 2:价格
//1:asc
String order = searchParam.getOrder();
if (!StringUtils.isEmpty(order)){
// 判断排序规则
String[] split = StringUtils.split(order, ":");
if (split!=null && split.length==2){
// 排序的字段
String field = null;
// 数组中的第一个参数
switch (split[0]){
case "1":
field="hotScore";
break;
case "2":
field="price";
break;
}
searchSourceBuilder.sort(field,"asc".equals(split[1])? SortOrder.ASC:SortOrder.DESC);
}else {
// 没有传值的时候给默认值
searchSourceBuilder.sort("hotScore",SortOrder.DESC);
}
}
// 构建高亮
HighlightBuilder highlightBuilder = new HighlightBuilder();
highlightBuilder.field("title");
highlightBuilder.postTags("</span>");
highlightBuilder.preTags("<span style=color:red>");
searchSourceBuilder.highlighter(highlightBuilder);
// 设置品牌聚合
TermsAggregationBuilder termsAggregationBuilder = AggregationBuilders.terms("tmIdAgg").field("tmId")
.subAggregation(AggregationBuilders.terms("tmNameAgg").field("tmName"))
.subAggregation(AggregationBuilders.terms("tmLogoUrlAgg").field("tmLogoUrl"));
searchSourceBuilder.aggregation(termsAggregationBuilder);
// 设置平台属性聚合
searchSourceBuilder.aggregation(AggregationBuilders.nested("attrAgg", "attrs")
.subAggregation(AggregationBuilders.terms("attrIdAgg").field("attrs.attrId")
.subAggregation(AggregationBuilders.terms("attrNameAgg").field("attrs.attrName"))
.subAggregation(AggregationBuilders.terms("attrValueAgg").field("attrs.attrValue"))));
// 结果集过滤
searchSourceBuilder.fetchSource(new String[]{"id","defaultImg","title","price"},null);
SearchRequest searchRequest = new SearchRequest("goods");
//searchRequest.types("_doc");
//将构建对象添加到请求中
searchRequest.source(searchSourceBuilder);
System.out.println("dsl:"+searchSourceBuilder.toString());
return searchRequest;
}
// 制作返回结果集
private SearchResponseVo parseSearchResult(SearchResponse response) {
SearchHits hits = response.getHits();
//声明对象
SearchResponseVo searchResponseVo = new SearchResponseVo();
//获取品牌的集合
Map<String, Aggregation> aggregationMap = response.getAggregations().asMap();
//ParsedLongTerms ?
ParsedLongTerms tmIdAgg = (ParsedLongTerms) aggregationMap.get("tmIdAgg");
List<SearchResponseTmVo> trademarkList = tmIdAgg.getBuckets().stream().map(bucket -> {
SearchResponseTmVo trademark = new SearchResponseTmVo();
//获取品牌Id
trademark.setTmId((Long.parseLong(((Terms.Bucket) bucket).getKeyAsString())));
//trademark.setTmId(Long.parseLong(bucket.getKeyAsString()));
//获取品牌名称
Map<String, Aggregation> tmIdSubMap = ((Terms.Bucket) bucket).getAggregations().asMap();
ParsedStringTerms tmNameAgg = (ParsedStringTerms) tmIdSubMap.get("tmNameAgg");
String tmName = tmNameAgg.getBuckets().get(0).getKeyAsString();
trademark.setTmName(tmName);
ParsedStringTerms tmLogoUrlAgg = (ParsedStringTerms) tmIdSubMap.get("tmLogoUrlAgg");
String tmLogoUrl = tmLogoUrlAgg.getBuckets().get(0).getKeyAsString();
trademark.setTmLogoUrl(tmLogoUrl);
return trademark;
}).collect(Collectors.toList());
searchResponseVo.setTrademarkList(trademarkList);
//赋值商品列表
SearchHit[] subHits = hits.getHits();
List<Goods> goodsList = new ArrayList<>();
if (subHits!=null && subHits.length>0){
//循环遍历
for (SearchHit subHit : subHits) {
// 将subHit 转换为对象
Goods goods = JSONObject.parseObject(subHit.getSourceAsString(), Goods.class);
//获取高亮
if (subHit.getHighlightFields().get("title")!=null){
Text title = subHit.getHighlightFields().get("title").getFragments()[0];
goods.setTitle(title.toString());
}
goodsList.add(goods);
}
}
searchResponseVo.setGoodsList(goodsList);
//获取平台属性数据
ParsedNested attrAgg = (ParsedNested) aggregationMap.get("attrAgg");
ParsedLongTerms attrIdAgg = attrAgg.getAggregations().get("attrIdAgg");
List<? extends Terms.Bucket> buckets = attrIdAgg.getBuckets();
if (!CollectionUtils.isEmpty(buckets)){
List<SearchResponseAttrVo> searchResponseAttrVOS = buckets.stream().map(bucket -> {
//声明平台属性对象
SearchResponseAttrVo responseAttrVO = new SearchResponseAttrVo();
//设置平台属性值Id
responseAttrVO.setAttrId(((Terms.Bucket) bucket).getKeyAsNumber().longValue());
ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attrNameAgg");
List<? extends Terms.Bucket> nameBuckets = attrNameAgg.getBuckets();
responseAttrVO.setAttrName(nameBuckets.get(0).getKeyAsString());
//设置规格参数列表
ParsedStringTerms attrValueAgg = ((Terms.Bucket) bucket).getAggregations().get("attrValueAgg");
List<? extends Terms.Bucket> valueBuckets = attrValueAgg.getBuckets();
List<String> values = valueBuckets.stream().map(Terms.Bucket::getKeyAsString).collect(Collectors.toList());
responseAttrVO.setAttrValueList(values);
return responseAttrVO;
}).collect(Collectors.toList());
searchResponseVo.setAttrsList(searchResponseAttrVOS);
}
// 获取总记录数
searchResponseVo.setTotal(hits.getTotalHits().value);
return searchResponseVo;
}
控制器ListApiController
/**
* 搜索商品
* @param searchParam
* @return
* @throws IOException
*/
@PostMapping
public Result list(@RequestBody SearchParam searchParam) throws IOException {
SearchResponseVo response = searchService.search(searchParam);
return Result.ok(response);
}
在service-list 模块中配置logstash
首先在service模块中添加依赖
<dependency>
<groupId>net.logstash.logback</groupId>
<artifactId>logstash-logback-encoder</artifactId>
<version>5.1</version>
</dependency>
其次,将日志配置文件放入到resources目录下!
(3)在service-list-client模块添加远程接口
package com.atguigu.gmall.list.client;
@FeignClient(value = "service-list", fallback = ListDegradeFeignClient.class)
public interface ListFeignClient {
/**
* 搜索商品
* @param listParam
* @return
*/
@PostMapping("/api/list")
Result list(@RequestBody SearchParam listParam);
/**
* 上架商品
* @param skuId
* @return
*/
@GetMapping("/api/list/inner/upperGoods/{skuId}")
Result upperGoods(@PathVariable("skuId") Long skuId);
/**
* 下架商品
* @param skuId
* @return
*/
@GetMapping("/api/list/inner/lowerGoods/{skuId}")
Result lowerGoods(@PathVariable("skuId") Long skuId);
}
package com.atguigu.gmall.list.client.impl;
@Component
public class ListDegradeFeignClient implements ListFeignClient {
@Override
public Result list(SearchParam searchParam) {
return Result.fail();
}
@Override
public Result upperGoods(Long skuId) {
return null;
}
@Override
public Result lowerGoods(Long skuId) {
return null;
}
}