商城服务es检索实现
This commit is contained in:
parent
7e3d927ec9
commit
642348c09e
|
|
@ -1,66 +1,67 @@
|
|||
import store from '@/store'
|
||||
import router from '@/router';
|
||||
|
||||
export default {
|
||||
// 刷新当前tab页签
|
||||
refreshPage(obj) {
|
||||
const { path, matched } = router.currentRoute;
|
||||
if (obj === undefined) {
|
||||
matched.forEach((m) => {
|
||||
if (m.components && m.components.default && m.components.default.name) {
|
||||
if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
|
||||
obj = { name: m.components.default.name, path: path };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return store.dispatch('tagsView/delCachedView', obj).then(() => {
|
||||
const { path } = obj
|
||||
router.replace({
|
||||
path: '/redirect' + path
|
||||
})
|
||||
})
|
||||
},
|
||||
// 关闭当前tab页签,打开新页签
|
||||
closeOpenPage(obj) {
|
||||
store.dispatch("tagsView/delView", router.currentRoute);
|
||||
if (obj !== undefined) {
|
||||
return router.push(obj);
|
||||
}
|
||||
},
|
||||
// 关闭指定tab页签
|
||||
closePage(obj) {
|
||||
if (obj === undefined) {
|
||||
return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => {
|
||||
return router.push(lastPath || '/');
|
||||
});
|
||||
}
|
||||
return store.dispatch('tagsView/delView', obj);
|
||||
},
|
||||
// 关闭所有tab页签
|
||||
closeAllPage() {
|
||||
return store.dispatch('tagsView/delAllViews');
|
||||
},
|
||||
// 关闭左侧tab页签
|
||||
closeLeftPage(obj) {
|
||||
return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute);
|
||||
},
|
||||
// 关闭右侧tab页签
|
||||
closeRightPage(obj) {
|
||||
return store.dispatch('tagsView/delRightTags', obj || router.currentRoute);
|
||||
},
|
||||
// 关闭其他tab页签
|
||||
closeOtherPage(obj) {
|
||||
return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute);
|
||||
},
|
||||
// 添加tab页签
|
||||
openPage(title, url) {
|
||||
var obj = { path: url, meta: { title: title } }
|
||||
store.dispatch('tagsView/addView', obj);
|
||||
return router.push(url);
|
||||
},
|
||||
// 修改tab页签
|
||||
updatePage(obj) {
|
||||
return store.dispatch('tagsView/updateVisitedView', obj);
|
||||
}
|
||||
}
|
||||
import store from '@/store'
|
||||
import router from '@/router';
|
||||
|
||||
export default {
|
||||
// 刷新当前tab页签
|
||||
refreshPage(obj) {
|
||||
const { path, query, matched } = router.currentRoute;
|
||||
if (obj === undefined) {
|
||||
matched.forEach((m) => {
|
||||
if (m.components && m.components.default && m.components.default.name) {
|
||||
if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
|
||||
obj = { name: m.components.default.name, path: path, query: query };
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
return store.dispatch('tagsView/delCachedView', obj).then(() => {
|
||||
const { path, query } = obj
|
||||
router.replace({
|
||||
path: '/redirect' + path,
|
||||
query: query
|
||||
})
|
||||
})
|
||||
},
|
||||
// 关闭当前tab页签,打开新页签
|
||||
closeOpenPage(obj) {
|
||||
store.dispatch("tagsView/delView", router.currentRoute);
|
||||
if (obj !== undefined) {
|
||||
return router.push(obj);
|
||||
}
|
||||
},
|
||||
// 关闭指定tab页签
|
||||
closePage(obj) {
|
||||
if (obj === undefined) {
|
||||
return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => {
|
||||
return router.push(lastPath || '/');
|
||||
});
|
||||
}
|
||||
return store.dispatch('tagsView/delView', obj);
|
||||
},
|
||||
// 关闭所有tab页签
|
||||
closeAllPage() {
|
||||
return store.dispatch('tagsView/delAllViews');
|
||||
},
|
||||
// 关闭左侧tab页签
|
||||
closeLeftPage(obj) {
|
||||
return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute);
|
||||
},
|
||||
// 关闭右侧tab页签
|
||||
closeRightPage(obj) {
|
||||
return store.dispatch('tagsView/delRightTags', obj || router.currentRoute);
|
||||
},
|
||||
// 关闭其他tab页签
|
||||
closeOtherPage(obj) {
|
||||
return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute);
|
||||
},
|
||||
// 添加tab页签
|
||||
openPage(title, url) {
|
||||
var obj = { path: url, meta: { title: title } }
|
||||
store.dispatch('tagsView/addView', obj);
|
||||
return router.push(url);
|
||||
},
|
||||
// 修改tab页签
|
||||
updatePage(obj) {
|
||||
return store.dispatch('tagsView/updateVisitedView', obj);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.xjs.consts;
|
|||
|
||||
/**
|
||||
* ES常量
|
||||
*
|
||||
* @author xiejs
|
||||
* @since 2022-04-06
|
||||
*/
|
||||
|
|
@ -9,5 +10,10 @@ public class EsConst {
|
|||
/**
|
||||
* sku数据在ES中的索引
|
||||
*/
|
||||
public static final String PRODUCT_INDEX = "product";
|
||||
public static final String PRODUCT_INDEX = "xjs_product";
|
||||
|
||||
|
||||
public static final int PRODUCT_PAGESIZE = 2;
|
||||
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ $(function(){
|
|||
var ctg3List=ctg2["catalog3List"];
|
||||
var len=0;
|
||||
$.each(ctg3List,function (i,ctg3) {
|
||||
var cata3link = $("<a href=\"http://search.gmall.com/list.html?catalog3Id="+ctg3.id+"\" style=\"color: #999;\">" + ctg3.name + "</a>");
|
||||
var cata3link = $("<a href=\"http://localhost:9986/search.html?catalog3Id="+ctg3.id+"\" style=\"color: #999;\">" + ctg3.name + "</a>");
|
||||
li.append(cata3link);
|
||||
len=len+1+ctg3.name.length;
|
||||
});
|
||||
|
|
@ -40,4 +40,4 @@ $(function(){
|
|||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -115,7 +115,7 @@
|
|||
<input id="searchText" type="text" placeholder=""/>
|
||||
<span style="background: url(../staticindex/img/img_12.png) 0 -1px;"></span>
|
||||
<!--<button><i class="glyphicon"></i></button>-->
|
||||
<a href="#"><img src="index/img/img_09.png" onclick="search()"/></a>
|
||||
<a href="javascript:search()"><img src="index/img/img_09.png" /></a>
|
||||
</div>
|
||||
<div class="header_ico">
|
||||
<div class="header_gw">
|
||||
|
|
@ -203,7 +203,7 @@
|
|||
<div class="header_main_left">
|
||||
<ul>
|
||||
<li th:each="category : ${categorys}">
|
||||
<a href="#" class="header_main_left_a" th:attr="ctg-data=${category.catId}"><b th:text="${category.name}"></b></a>
|
||||
<a href="http://localhost:9986/" class="header_main_left_a" th:attr="ctg-data=${category.catId}"><b th:text="${category.name}"></b></a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
|
|
@ -611,7 +611,7 @@
|
|||
<script type="text/javascript">
|
||||
function search() {
|
||||
var keyword = $("#searchText").val()
|
||||
window.location.href = "http://search.gulimall.com/search.html?keyword=" + keyword;
|
||||
window.location.href = "http://localhost:9986//search.html?keyword=" + keyword;
|
||||
}
|
||||
|
||||
</script>
|
||||
|
|
|
|||
|
|
@ -0,0 +1,37 @@
|
|||
package com.xjs.mall.search.controller;
|
||||
|
||||
import com.xjs.mall.search.service.MallSearchService;
|
||||
import com.xjs.mall.search.vo.SearchParam;
|
||||
import com.xjs.mall.search.vo.SearchResult;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.ui.Model;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-05-11
|
||||
*/
|
||||
@Controller
|
||||
public class SearchController {
|
||||
|
||||
@Autowired
|
||||
MallSearchService mallSearchService;
|
||||
|
||||
/**
|
||||
* 自动将页面提交过来的所有请求查询参数封装成指定的对象
|
||||
*
|
||||
* @param param 检索条件
|
||||
* @return url
|
||||
*/
|
||||
@GetMapping("/search.html")
|
||||
public String listPage(SearchParam param, Model model) {
|
||||
//1、根据传递来的页面的查询参数,去es检索商品
|
||||
SearchResult result = mallSearchService.search(param);
|
||||
|
||||
model.addAttribute("result", result);
|
||||
|
||||
return "list";
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package com.xjs.mall.search.service;
|
||||
|
||||
import com.xjs.mall.search.vo.SearchParam;
|
||||
import com.xjs.mall.search.vo.SearchResult;
|
||||
|
||||
/**
|
||||
* @author xiejs
|
||||
* @since 2022-05-11
|
||||
*/
|
||||
public interface MallSearchService {
|
||||
|
||||
/**
|
||||
*
|
||||
* @param searchParam 检索的所有参数
|
||||
* @return obj
|
||||
*/
|
||||
SearchResult search(SearchParam searchParam);
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,370 @@
|
|||
package com.xjs.mall.search.service.impl;
|
||||
|
||||
import cn.hutool.core.collection.CollUtil;
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.ruoyi.common.core.utils.StringUtils;
|
||||
import com.xjs.consts.EsConst;
|
||||
import com.xjs.mall.search.config.ElasticsearchConfig;
|
||||
import com.xjs.mall.search.service.MallSearchService;
|
||||
import com.xjs.mall.search.vo.SearchParam;
|
||||
import com.xjs.mall.search.vo.SearchResult;
|
||||
import com.xjs.mall.to.es.SkuEsModel;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.lucene.search.join.ScoreMode;
|
||||
import org.elasticsearch.action.search.SearchRequest;
|
||||
import org.elasticsearch.action.search.SearchResponse;
|
||||
import org.elasticsearch.client.RestHighLevelClient;
|
||||
import org.elasticsearch.index.query.BoolQueryBuilder;
|
||||
import org.elasticsearch.index.query.NestedQueryBuilder;
|
||||
import org.elasticsearch.index.query.QueryBuilders;
|
||||
import org.elasticsearch.index.query.RangeQueryBuilder;
|
||||
import org.elasticsearch.search.SearchHit;
|
||||
import org.elasticsearch.search.SearchHits;
|
||||
import org.elasticsearch.search.aggregations.AggregationBuilders;
|
||||
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
|
||||
import org.elasticsearch.search.aggregations.bucket.nested.NestedAggregationBuilder;
|
||||
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.aggregations.bucket.terms.TermsAggregationBuilder;
|
||||
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 java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* 商城检索service
|
||||
*
|
||||
* @author xiejs
|
||||
* @since 2022-05-11
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class MallSearchServiceImpl implements MallSearchService {
|
||||
|
||||
@Autowired
|
||||
private RestHighLevelClient client;
|
||||
|
||||
@Override
|
||||
public SearchResult search(SearchParam searchParam) {
|
||||
//动态构建查询需要的DSL语句
|
||||
|
||||
//1、准备检索请求
|
||||
SearchRequest searchRequest = this.buildSearchRequest(searchParam);
|
||||
|
||||
SearchResult searchResult = null;
|
||||
|
||||
try {
|
||||
//2、执行检索请求
|
||||
SearchResponse response = client.search(searchRequest, ElasticsearchConfig.COMMON_OPTIONS);
|
||||
|
||||
//3、分析响应数据封装成需要的数据
|
||||
searchResult = this.buildSearchResult(response,searchParam);
|
||||
|
||||
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
|
||||
return searchResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建请求数据
|
||||
*
|
||||
* @return 请求数据
|
||||
*/
|
||||
private SearchRequest buildSearchRequest(SearchParam searchParam) {
|
||||
//构建DSL语句
|
||||
SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
|
||||
|
||||
//模糊匹配,过滤(按照属性、分类、品牌、价格区间、库存)
|
||||
|
||||
//1、构建bool-query
|
||||
BoolQueryBuilder boolQuery = QueryBuilders.boolQuery();
|
||||
|
||||
//1.1、must模糊匹配
|
||||
if (!StringUtils.isEmpty(searchParam.getKeyword())) {
|
||||
boolQuery.must(QueryBuilders.matchQuery("skuTitle", searchParam.getKeyword()));
|
||||
}
|
||||
|
||||
//1.2、bool-filter 按照三级分类id查询
|
||||
if (searchParam.getCatalog3Id() != null) {
|
||||
boolQuery.filter(QueryBuilders.termQuery("catalogId", searchParam.getCatalog3Id()));
|
||||
}
|
||||
|
||||
//1.2、bool-filter 按照品牌id查询
|
||||
if (CollUtil.isNotEmpty(searchParam.getBrandId())) {
|
||||
boolQuery.filter(QueryBuilders.termQuery("brandId", searchParam.getBrandId()));
|
||||
}
|
||||
|
||||
//1.2、bool-filter 按照是否有库存查询
|
||||
boolQuery.filter(QueryBuilders.termQuery("hasStock", searchParam.getHasStock() == 1));
|
||||
|
||||
//1.2、bool-filter 按照价格区间查询
|
||||
if (StringUtils.isNotEmpty(searchParam.getSkuPrice())) {
|
||||
RangeQueryBuilder rangeQuery = QueryBuilders.rangeQuery("skuPrice");
|
||||
String[] split = searchParam.getSkuPrice().split("_");
|
||||
if (split.length == 2) {
|
||||
//区间
|
||||
rangeQuery.gte(split[0]).lte(split[1]);
|
||||
} else if (split.length == 1) {
|
||||
if (searchParam.getSkuPrice().startsWith("_")) {
|
||||
rangeQuery.lte(split[0]);
|
||||
}
|
||||
if (searchParam.getSkuPrice().endsWith("_")) {
|
||||
rangeQuery.gt(split[0]);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
boolQuery.filter(rangeQuery);
|
||||
}
|
||||
|
||||
//1.2、bool-filter 按照所有指定的属性查询
|
||||
if (CollUtil.isNotEmpty(searchParam.getAttrs())) {
|
||||
|
||||
for (String attrStr : searchParam.getAttrs()) {
|
||||
BoolQueryBuilder nestedBoolQuery = QueryBuilders.boolQuery();
|
||||
|
||||
String[] split = attrStr.split("_");
|
||||
String attrId = split[0]; //检索的属性id
|
||||
String[] attrValues = split[1].split(";"); //属性的检索用的值
|
||||
nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrId", attrId));
|
||||
nestedBoolQuery.must(QueryBuilders.termQuery("attrs.attrValue", attrValues));
|
||||
|
||||
//每一个必须都得生成一个nested查询
|
||||
NestedQueryBuilder nestedQuery = QueryBuilders.nestedQuery("attrs", nestedBoolQuery, ScoreMode.None);
|
||||
boolQuery.filter(nestedQuery);
|
||||
}
|
||||
}
|
||||
|
||||
sourceBuilder.query(boolQuery);
|
||||
|
||||
|
||||
//排序、分页、高亮
|
||||
|
||||
//2.1、排序
|
||||
String sort = searchParam.getSort();
|
||||
if (StringUtils.isNotEmpty(sort)) {
|
||||
String[] split = sort.split("_");
|
||||
SortOrder order = split[1].equalsIgnoreCase("asc") ? SortOrder.ASC : SortOrder.DESC;
|
||||
sourceBuilder.sort(split[0], order);
|
||||
}
|
||||
|
||||
//2.2、分页
|
||||
sourceBuilder.from((searchParam.getPageNum() - 1) * EsConst.PRODUCT_PAGESIZE);
|
||||
sourceBuilder.size(EsConst.PRODUCT_PAGESIZE);
|
||||
|
||||
//2.3、高亮
|
||||
if (StringUtils.isNotEmpty(searchParam.getKeyword())) {
|
||||
HighlightBuilder highlightBuilder = new HighlightBuilder();
|
||||
highlightBuilder.field("skuTitle");
|
||||
highlightBuilder.preTags("<b style='color:red'>");
|
||||
highlightBuilder.postTags("</b>");
|
||||
sourceBuilder.highlighter(highlightBuilder);
|
||||
}
|
||||
|
||||
|
||||
//聚合分析
|
||||
|
||||
//1. 按照品牌进行聚合
|
||||
TermsAggregationBuilder brand_agg = AggregationBuilders.terms("brand_agg");
|
||||
brand_agg.field("brandId").size(50);
|
||||
|
||||
//1.1 品牌的子聚合-品牌名聚合
|
||||
brand_agg.subAggregation(AggregationBuilders.terms("brand_name_agg").field("brandName").size(1));
|
||||
//1.2 品牌的子聚合-品牌图片聚合
|
||||
brand_agg.subAggregation(AggregationBuilders.terms("brand_img_agg").field("brandImg").size(1));
|
||||
sourceBuilder.aggregation(brand_agg);
|
||||
|
||||
//2. 按照分类信息进行聚合
|
||||
TermsAggregationBuilder catalog_agg = AggregationBuilders.terms("catalog_agg");
|
||||
catalog_agg.field("catalogId").size(20);
|
||||
catalog_agg.subAggregation(AggregationBuilders.terms("catalog_name_agg").field("catalogName").size(1));
|
||||
sourceBuilder.aggregation(catalog_agg);
|
||||
|
||||
// 3. 按照属性信息进行聚合
|
||||
NestedAggregationBuilder attr_agg = AggregationBuilders.nested("attr_agg", "attrs");
|
||||
//3.1 按照属性ID进行聚合
|
||||
TermsAggregationBuilder attr_id_agg = AggregationBuilders.terms("attr_id_agg").field("attrs.attrId");
|
||||
attr_agg.subAggregation(attr_id_agg);
|
||||
//3.1.1 在每个属性ID下,按照属性名进行聚合
|
||||
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_name_agg").field("attrs.attrName").size(1));
|
||||
//3.1.2 在每个属性ID下,按照属性值进行聚合
|
||||
attr_id_agg.subAggregation(AggregationBuilders.terms("attr_value_agg").field("attrs.attrValue").size(50));
|
||||
sourceBuilder.aggregation(attr_agg);
|
||||
|
||||
log.info(sourceBuilder.toString());
|
||||
|
||||
return new SearchRequest(new String[]{EsConst.PRODUCT_INDEX}, sourceBuilder);
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建结果数据
|
||||
*
|
||||
* @param response 返回数据
|
||||
* @return 返回数据
|
||||
*/
|
||||
private SearchResult buildSearchResult(SearchResponse response, SearchParam param) {
|
||||
SearchResult result = new SearchResult();
|
||||
|
||||
//1、返回的所有查询到的商品
|
||||
SearchHits hits = response.getHits();
|
||||
|
||||
List<SkuEsModel> esModels = new ArrayList<>();
|
||||
//遍历所有商品信息
|
||||
if (hits.getHits() != null && hits.getHits().length > 0) {
|
||||
for (SearchHit hit : hits.getHits()) {
|
||||
String sourceAsString = hit.getSourceAsString();
|
||||
SkuEsModel esModel = JSON.parseObject(sourceAsString, SkuEsModel.class);
|
||||
|
||||
//判断是否按关键字检索,若是就显示高亮,否则不显示
|
||||
if (!StringUtils.isEmpty(param.getKeyword())) {
|
||||
//拿到高亮信息显示标题
|
||||
HighlightField skuTitle = hit.getHighlightFields().get("skuTitle");
|
||||
String skuTitleValue = skuTitle.getFragments()[0].string();
|
||||
esModel.setSkuTitle(skuTitleValue);
|
||||
}
|
||||
esModels.add(esModel);
|
||||
}
|
||||
}
|
||||
result.setProducts(esModels);
|
||||
|
||||
//2、当前商品涉及到的所有属性信息
|
||||
List<SearchResult.AttrVo> attrVos = new ArrayList<>();
|
||||
//获取属性信息的聚合
|
||||
ParsedNested attrsAgg = response.getAggregations().get("attr_agg");
|
||||
ParsedLongTerms attrIdAgg = attrsAgg.getAggregations().get("attr_id_agg");
|
||||
for (Terms.Bucket bucket : attrIdAgg.getBuckets()) {
|
||||
SearchResult.AttrVo attrVo = new SearchResult.AttrVo();
|
||||
//1、得到属性的id
|
||||
long attrId = bucket.getKeyAsNumber().longValue();
|
||||
attrVo.setAttrId(attrId);
|
||||
|
||||
//2、得到属性的名字
|
||||
ParsedStringTerms attrNameAgg = bucket.getAggregations().get("attr_name_agg");
|
||||
String attrName = attrNameAgg.getBuckets().get(0).getKeyAsString();
|
||||
attrVo.setAttrName(attrName);
|
||||
|
||||
//3、得到属性的所有值
|
||||
ParsedStringTerms attrValueAgg = bucket.getAggregations().get("attr_value_agg");
|
||||
List<String> attrValues = attrValueAgg.getBuckets().stream().map(MultiBucketsAggregation.Bucket::getKeyAsString).collect(Collectors.toList());
|
||||
attrVo.setAttrValue(attrValues);
|
||||
|
||||
attrVos.add(attrVo);
|
||||
}
|
||||
|
||||
result.setAttrs(attrVos);
|
||||
|
||||
//3、当前商品涉及到的所有品牌信息
|
||||
List<SearchResult.BrandVo> brandVos = new ArrayList<>();
|
||||
//获取到品牌的聚合
|
||||
ParsedLongTerms brandAgg = response.getAggregations().get("brand_agg");
|
||||
for (Terms.Bucket bucket : brandAgg.getBuckets()) {
|
||||
SearchResult.BrandVo brandVo = new SearchResult.BrandVo();
|
||||
|
||||
//1、得到品牌的id
|
||||
long brandId = bucket.getKeyAsNumber().longValue();
|
||||
brandVo.setBrandId(brandId);
|
||||
|
||||
//2、得到品牌的名字
|
||||
ParsedStringTerms brandNameAgg = bucket.getAggregations().get("brand_name_agg");
|
||||
String brandName = brandNameAgg.getBuckets().get(0).getKeyAsString();
|
||||
brandVo.setBrandName(brandName);
|
||||
|
||||
//3、得到品牌的图片
|
||||
ParsedStringTerms brandImgAgg = bucket.getAggregations().get("brand_img_agg");
|
||||
String brandImg = brandImgAgg.getBuckets().get(0).getKeyAsString();
|
||||
brandVo.setBrandImg(brandImg);
|
||||
|
||||
brandVos.add(brandVo);
|
||||
}
|
||||
result.setBrands(brandVos);
|
||||
|
||||
//4、当前商品涉及到的所有分类信息
|
||||
//获取到分类的聚合
|
||||
List<SearchResult.CatalogVo> catalogVos = new ArrayList<>();
|
||||
ParsedLongTerms catalogAgg = response.getAggregations().get("catalog_agg");
|
||||
for (Terms.Bucket bucket : catalogAgg.getBuckets()) {
|
||||
SearchResult.CatalogVo catalogVo = new SearchResult.CatalogVo();
|
||||
//得到分类id
|
||||
String keyAsString = bucket.getKeyAsString();
|
||||
catalogVo.setCatalogId(Long.parseLong(keyAsString));
|
||||
|
||||
//得到分类名
|
||||
ParsedStringTerms catalogNameAgg = bucket.getAggregations().get("catalog_name_agg");
|
||||
String catalogName = catalogNameAgg.getBuckets().get(0).getKeyAsString();
|
||||
catalogVo.setCatalogName(catalogName);
|
||||
catalogVos.add(catalogVo);
|
||||
}
|
||||
|
||||
result.setCatalogs(catalogVos);
|
||||
//===============以上可以从聚合信息中获取====================//
|
||||
|
||||
//5、分页信息-页码
|
||||
result.setPageNum(param.getPageNum());
|
||||
//5、1分页信息、总记录数
|
||||
long total = hits.getTotalHits().value;
|
||||
result.setTotal(total);
|
||||
|
||||
//5、2分页信息-总页码-计算
|
||||
int totalPages = (int) total % EsConst.PRODUCT_PAGESIZE == 0 ?
|
||||
(int) total / EsConst.PRODUCT_PAGESIZE : ((int) total / EsConst.PRODUCT_PAGESIZE + 1);
|
||||
result.setTotalPages(totalPages);
|
||||
|
||||
List<Integer> pageNavs = new ArrayList<>();
|
||||
for (int i = 1; i <= totalPages; i++) {
|
||||
pageNavs.add(i);
|
||||
}
|
||||
result.setPageNavs(pageNavs);
|
||||
|
||||
//6、构建面包屑导航
|
||||
/*if (param.getAttrs() != null && param.getAttrs().size() > 0) {
|
||||
List<SearchResult.NavVo> collect = param.getAttrs().stream().map(attr -> {
|
||||
//1、分析每一个attrs传过来的参数值
|
||||
SearchResult.NavVo navVo = new SearchResult.NavVo();
|
||||
String[] s = attr.split("_");
|
||||
navVo.setNavValue(s[1]);
|
||||
R r = productFeignService.attrInfo(Long.parseLong(s[0]));
|
||||
if (r.getCode() == 0) {
|
||||
AttrResponseVo data = r.getData("attr", new TypeReference<AttrResponseVo>() {
|
||||
});
|
||||
navVo.setNavName(data.getAttrName());
|
||||
} else {
|
||||
navVo.setNavName(s[0]);
|
||||
}
|
||||
|
||||
//2、取消了这个面包屑以后,我们要跳转到哪个地方,将请求的地址url里面的当前置空
|
||||
//拿到所有的查询条件,去掉当前
|
||||
String encode = null;
|
||||
try {
|
||||
encode = URLEncoder.encode(attr, "UTF-8");
|
||||
encode.replace("+", "%20"); //浏览器对空格的编码和Java不一样,差异化处理
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
String replace = param.get_queryString().replace("&attrs=" + attr, "");
|
||||
navVo.setLink("http://search.gulimall.com/list.html?" + replace);
|
||||
|
||||
return navVo;
|
||||
}).collect(Collectors.toList());
|
||||
|
||||
result.setNavs(collect);
|
||||
}*/
|
||||
|
||||
log.info(result.toString());
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,64 @@
|
|||
package com.xjs.mall.search.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* es 检索条件实体
|
||||
*
|
||||
* @author xiejs
|
||||
* @since 2022-05-11
|
||||
*/
|
||||
@Data
|
||||
public class SearchParam {
|
||||
|
||||
/**
|
||||
* 页面传递的全文匹配关键字
|
||||
*/
|
||||
private String keyword;
|
||||
|
||||
/**
|
||||
* 三级分类id
|
||||
*/
|
||||
private Long catalog3Id;
|
||||
|
||||
/**
|
||||
* 排序条件 saleCount_asc/desc skuPrice_asc/desc hotScore_asc/desc
|
||||
*/
|
||||
private String sort;
|
||||
|
||||
|
||||
/**
|
||||
* 过滤条件
|
||||
* hasStock(是否有货) 、skuPrice区间 、 brandId 、 catalog3Id 、 attrs
|
||||
*/
|
||||
|
||||
|
||||
/**
|
||||
* 是否只显示有货(0:无库存 1:有库存)
|
||||
*/
|
||||
private Integer hasStock = 1;
|
||||
|
||||
/**
|
||||
* 价格区间查询
|
||||
*/
|
||||
private String skuPrice;
|
||||
|
||||
/**
|
||||
* 品牌id
|
||||
*/
|
||||
private List<Long> brandId;
|
||||
|
||||
/**
|
||||
* 按照属性进行筛选
|
||||
*/
|
||||
private List<String> attrs;
|
||||
|
||||
/**
|
||||
* 页码
|
||||
*/
|
||||
private Integer pageNum = 1;
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,120 @@
|
|||
package com.xjs.mall.search.vo;
|
||||
|
||||
import com.xjs.mall.to.es.SkuEsModel;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 检索响应信息实体
|
||||
*
|
||||
* @author xiejs
|
||||
* @since 2022-05-11
|
||||
*/
|
||||
@Data
|
||||
public class SearchResult {
|
||||
|
||||
/**
|
||||
* 查询到的所有商品信息
|
||||
*/
|
||||
private List<SkuEsModel> products;
|
||||
|
||||
/**
|
||||
* 当前页面
|
||||
*/
|
||||
private Integer pageNum;
|
||||
|
||||
/**
|
||||
* 总数
|
||||
*/
|
||||
private Long total;
|
||||
|
||||
/**
|
||||
* 总页码
|
||||
*/
|
||||
private Integer totalPages;
|
||||
|
||||
/**
|
||||
* 当前结果查询的所有品牌
|
||||
*/
|
||||
private List<BrandVo> brands;
|
||||
|
||||
/**
|
||||
* 当前结果查询的所有属性
|
||||
*/
|
||||
private List<AttrVo> attrs;
|
||||
|
||||
/**
|
||||
* 当前结果查询的所有分类
|
||||
*/
|
||||
private List<CatalogVo> catalogs;
|
||||
|
||||
private List<Integer> pageNavs;
|
||||
|
||||
//================以上是返回给页面的所有信息=========================
|
||||
|
||||
/* 面包屑导航数据 */
|
||||
private List<NavVo> navs;
|
||||
|
||||
@Data
|
||||
public static class NavVo {
|
||||
private String navName;
|
||||
private String navValue;
|
||||
private String link;
|
||||
}
|
||||
|
||||
|
||||
@Data
|
||||
public static class BrandVo {
|
||||
/**
|
||||
* 品牌id
|
||||
*/
|
||||
private Long brandId;
|
||||
|
||||
/**
|
||||
* 品牌名
|
||||
*/
|
||||
private String brandName;
|
||||
|
||||
/**
|
||||
* 品牌logo
|
||||
*/
|
||||
private String brandImg;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class AttrVo {
|
||||
|
||||
/**
|
||||
* 属性id
|
||||
*/
|
||||
private Long attrId;
|
||||
|
||||
/**
|
||||
* 属性名
|
||||
*/
|
||||
private String attrName;
|
||||
|
||||
/**
|
||||
* 属性值
|
||||
*/
|
||||
private List<String> attrValue;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class CatalogVo {
|
||||
|
||||
/**
|
||||
* 分类ID
|
||||
*/
|
||||
private Long catalogId;
|
||||
|
||||
/**
|
||||
* 分类名称
|
||||
*/
|
||||
private String catalogName;
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,99 @@
|
|||
{
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [ {"match": { "skuTitle": "华为" }} ],
|
||||
"filter": [
|
||||
{ "term": { "catalogId": "225" } },
|
||||
{ "terms": {"brandId": [ "2"] } },
|
||||
{ "term": { "hasStock": "false"} },
|
||||
{
|
||||
"range": {
|
||||
"skuPrice": {
|
||||
"gte": 1000,
|
||||
"lte": 7000
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"nested": {
|
||||
"path": "attrs",
|
||||
"query": {
|
||||
"bool": {
|
||||
"must": [
|
||||
{
|
||||
"term": { "attrs.attrId": { "value": "6"} }
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"sort": [ {"skuPrice": {"order": "desc" } } ],
|
||||
"from": 0,
|
||||
"size": 5,
|
||||
"highlight": {
|
||||
"fields": {"skuTitle": {}},
|
||||
"pre_tags": "<b style='color:red'>",
|
||||
"post_tags": "</b>"
|
||||
},
|
||||
"aggs": {
|
||||
"brandAgg": {
|
||||
"terms": {
|
||||
"field": "brandId",
|
||||
"size": 10
|
||||
},
|
||||
"aggs": {
|
||||
"brandNameAgg": {
|
||||
"terms": {
|
||||
"field": "brandName",
|
||||
"size": 10
|
||||
}
|
||||
},
|
||||
|
||||
"brandImgAgg": {
|
||||
"terms": {
|
||||
"field": "brandImg",
|
||||
"size": 10
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
},
|
||||
"catalogAgg":{
|
||||
"terms": {
|
||||
"field": "catalogId",
|
||||
"size": 10
|
||||
},
|
||||
"aggs": {
|
||||
"catalogNameAgg": {
|
||||
"terms": {
|
||||
"field": "catalogName",
|
||||
"size": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"attrs":{
|
||||
"nested": {"path": "attrs" },
|
||||
"aggs": {
|
||||
"attrIdAgg": {
|
||||
"terms": {
|
||||
"field": "attrs.attrId",
|
||||
"size": 10
|
||||
},
|
||||
"aggs": {
|
||||
"attrNameAgg": {
|
||||
"terms": {
|
||||
"field": "attrs.attrName",
|
||||
"size": 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -15,14 +15,12 @@
|
|||
"type": "keyword"
|
||||
},
|
||||
"skuImg": {
|
||||
"type": "keyword",
|
||||
"index": false,
|
||||
"doc_values": false
|
||||
"type": "keyword"
|
||||
},
|
||||
"saleCount": {
|
||||
"type": "long"
|
||||
},
|
||||
"hasStock": {
|
||||
"hosStock": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"hotScore": {
|
||||
|
|
@ -34,19 +32,14 @@
|
|||
"catalogId": {
|
||||
"type": "long"
|
||||
},
|
||||
"catalogName": {
|
||||
"type": "text",
|
||||
"analyzer": "ik_smart"
|
||||
},
|
||||
"brandName": {
|
||||
"type": "keyword",
|
||||
"index": false,
|
||||
"doc_values": false
|
||||
"type": "keyword"
|
||||
},
|
||||
"brandImg": {
|
||||
"type": "keyword",
|
||||
"index": false,
|
||||
"doc_values": true
|
||||
"type": "keyword"
|
||||
},
|
||||
"catalogName": {
|
||||
"type": "keyword"
|
||||
},
|
||||
"attrs": {
|
||||
"type": "nested",
|
||||
|
|
@ -55,9 +48,7 @@
|
|||
"type": "long"
|
||||
},
|
||||
"attrName": {
|
||||
"type": "keyword",
|
||||
"index": false,
|
||||
"doc_values": false
|
||||
"type": "keyword"
|
||||
},
|
||||
"attrValue": {
|
||||
"type": "keyword"
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue