商城服务es检索实现
This commit is contained in:
parent
7e3d927ec9
commit
642348c09e
|
|
@ -1,66 +1,67 @@
|
||||||
import store from '@/store'
|
import store from '@/store'
|
||||||
import router from '@/router';
|
import router from '@/router';
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
// 刷新当前tab页签
|
// 刷新当前tab页签
|
||||||
refreshPage(obj) {
|
refreshPage(obj) {
|
||||||
const { path, matched } = router.currentRoute;
|
const { path, query, matched } = router.currentRoute;
|
||||||
if (obj === undefined) {
|
if (obj === undefined) {
|
||||||
matched.forEach((m) => {
|
matched.forEach((m) => {
|
||||||
if (m.components && m.components.default && m.components.default.name) {
|
if (m.components && m.components.default && m.components.default.name) {
|
||||||
if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
|
if (!['Layout', 'ParentView'].includes(m.components.default.name)) {
|
||||||
obj = { name: m.components.default.name, path: path };
|
obj = { name: m.components.default.name, path: path, query: query };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return store.dispatch('tagsView/delCachedView', obj).then(() => {
|
return store.dispatch('tagsView/delCachedView', obj).then(() => {
|
||||||
const { path } = obj
|
const { path, query } = obj
|
||||||
router.replace({
|
router.replace({
|
||||||
path: '/redirect' + path
|
path: '/redirect' + path,
|
||||||
})
|
query: query
|
||||||
})
|
})
|
||||||
},
|
})
|
||||||
// 关闭当前tab页签,打开新页签
|
},
|
||||||
closeOpenPage(obj) {
|
// 关闭当前tab页签,打开新页签
|
||||||
store.dispatch("tagsView/delView", router.currentRoute);
|
closeOpenPage(obj) {
|
||||||
if (obj !== undefined) {
|
store.dispatch("tagsView/delView", router.currentRoute);
|
||||||
return router.push(obj);
|
if (obj !== undefined) {
|
||||||
}
|
return router.push(obj);
|
||||||
},
|
}
|
||||||
// 关闭指定tab页签
|
},
|
||||||
closePage(obj) {
|
// 关闭指定tab页签
|
||||||
if (obj === undefined) {
|
closePage(obj) {
|
||||||
return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => {
|
if (obj === undefined) {
|
||||||
return router.push(lastPath || '/');
|
return store.dispatch('tagsView/delView', router.currentRoute).then(({ lastPath }) => {
|
||||||
});
|
return router.push(lastPath || '/');
|
||||||
}
|
});
|
||||||
return store.dispatch('tagsView/delView', obj);
|
}
|
||||||
},
|
return store.dispatch('tagsView/delView', obj);
|
||||||
// 关闭所有tab页签
|
},
|
||||||
closeAllPage() {
|
// 关闭所有tab页签
|
||||||
return store.dispatch('tagsView/delAllViews');
|
closeAllPage() {
|
||||||
},
|
return store.dispatch('tagsView/delAllViews');
|
||||||
// 关闭左侧tab页签
|
},
|
||||||
closeLeftPage(obj) {
|
// 关闭左侧tab页签
|
||||||
return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute);
|
closeLeftPage(obj) {
|
||||||
},
|
return store.dispatch('tagsView/delLeftTags', obj || router.currentRoute);
|
||||||
// 关闭右侧tab页签
|
},
|
||||||
closeRightPage(obj) {
|
// 关闭右侧tab页签
|
||||||
return store.dispatch('tagsView/delRightTags', obj || router.currentRoute);
|
closeRightPage(obj) {
|
||||||
},
|
return store.dispatch('tagsView/delRightTags', obj || router.currentRoute);
|
||||||
// 关闭其他tab页签
|
},
|
||||||
closeOtherPage(obj) {
|
// 关闭其他tab页签
|
||||||
return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute);
|
closeOtherPage(obj) {
|
||||||
},
|
return store.dispatch('tagsView/delOthersViews', obj || router.currentRoute);
|
||||||
// 添加tab页签
|
},
|
||||||
openPage(title, url) {
|
// 添加tab页签
|
||||||
var obj = { path: url, meta: { title: title } }
|
openPage(title, url) {
|
||||||
store.dispatch('tagsView/addView', obj);
|
var obj = { path: url, meta: { title: title } }
|
||||||
return router.push(url);
|
store.dispatch('tagsView/addView', obj);
|
||||||
},
|
return router.push(url);
|
||||||
// 修改tab页签
|
},
|
||||||
updatePage(obj) {
|
// 修改tab页签
|
||||||
return store.dispatch('tagsView/updateVisitedView', obj);
|
updatePage(obj) {
|
||||||
}
|
return store.dispatch('tagsView/updateVisitedView', obj);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,6 +2,7 @@ package com.xjs.consts;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* ES常量
|
* ES常量
|
||||||
|
*
|
||||||
* @author xiejs
|
* @author xiejs
|
||||||
* @since 2022-04-06
|
* @since 2022-04-06
|
||||||
*/
|
*/
|
||||||
|
|
@ -9,5 +10,10 @@ public class EsConst {
|
||||||
/**
|
/**
|
||||||
* sku数据在ES中的索引
|
* 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 ctg3List=ctg2["catalog3List"];
|
||||||
var len=0;
|
var len=0;
|
||||||
$.each(ctg3List,function (i,ctg3) {
|
$.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);
|
li.append(cata3link);
|
||||||
len=len+1+ctg3.name.length;
|
len=len+1+ctg3.name.length;
|
||||||
});
|
});
|
||||||
|
|
@ -40,4 +40,4 @@ $(function(){
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@
|
||||||
<input id="searchText" type="text" placeholder=""/>
|
<input id="searchText" type="text" placeholder=""/>
|
||||||
<span style="background: url(../staticindex/img/img_12.png) 0 -1px;"></span>
|
<span style="background: url(../staticindex/img/img_12.png) 0 -1px;"></span>
|
||||||
<!--<button><i class="glyphicon"></i></button>-->
|
<!--<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>
|
||||||
<div class="header_ico">
|
<div class="header_ico">
|
||||||
<div class="header_gw">
|
<div class="header_gw">
|
||||||
|
|
@ -203,7 +203,7 @@
|
||||||
<div class="header_main_left">
|
<div class="header_main_left">
|
||||||
<ul>
|
<ul>
|
||||||
<li th:each="category : ${categorys}">
|
<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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
|
|
||||||
|
|
@ -611,7 +611,7 @@
|
||||||
<script type="text/javascript">
|
<script type="text/javascript">
|
||||||
function search() {
|
function search() {
|
||||||
var keyword = $("#searchText").val()
|
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>
|
</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"
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"skuImg": {
|
"skuImg": {
|
||||||
"type": "keyword",
|
"type": "keyword"
|
||||||
"index": false,
|
|
||||||
"doc_values": false
|
|
||||||
},
|
},
|
||||||
"saleCount": {
|
"saleCount": {
|
||||||
"type": "long"
|
"type": "long"
|
||||||
},
|
},
|
||||||
"hasStock": {
|
"hosStock": {
|
||||||
"type": "boolean"
|
"type": "boolean"
|
||||||
},
|
},
|
||||||
"hotScore": {
|
"hotScore": {
|
||||||
|
|
@ -34,19 +32,14 @@
|
||||||
"catalogId": {
|
"catalogId": {
|
||||||
"type": "long"
|
"type": "long"
|
||||||
},
|
},
|
||||||
"catalogName": {
|
|
||||||
"type": "text",
|
|
||||||
"analyzer": "ik_smart"
|
|
||||||
},
|
|
||||||
"brandName": {
|
"brandName": {
|
||||||
"type": "keyword",
|
"type": "keyword"
|
||||||
"index": false,
|
|
||||||
"doc_values": false
|
|
||||||
},
|
},
|
||||||
"brandImg": {
|
"brandImg": {
|
||||||
"type": "keyword",
|
"type": "keyword"
|
||||||
"index": false,
|
},
|
||||||
"doc_values": true
|
"catalogName": {
|
||||||
|
"type": "keyword"
|
||||||
},
|
},
|
||||||
"attrs": {
|
"attrs": {
|
||||||
"type": "nested",
|
"type": "nested",
|
||||||
|
|
@ -55,9 +48,7 @@
|
||||||
"type": "long"
|
"type": "long"
|
||||||
},
|
},
|
||||||
"attrName": {
|
"attrName": {
|
||||||
"type": "keyword",
|
"type": "keyword"
|
||||||
"index": false,
|
|
||||||
"doc_values": false
|
|
||||||
},
|
},
|
||||||
"attrValue": {
|
"attrValue": {
|
||||||
"type": "keyword"
|
"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