|
|
package com.yoho.search.service.servicenew.impl;
|
|
|
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
import java.util.Collections;
|
|
|
import java.util.Iterator;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
|
|
|
import org.apache.commons.lang.StringUtils;
|
|
|
import org.elasticsearch.index.query.BoolQueryBuilder;
|
|
|
import org.elasticsearch.index.query.QueryBuilder;
|
|
|
import org.elasticsearch.index.query.QueryBuilders;
|
|
|
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
|
|
|
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
|
|
|
import org.elasticsearch.search.SearchHit;
|
|
|
import org.elasticsearch.search.SearchHits;
|
|
|
import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
|
|
|
import org.elasticsearch.search.aggregations.Aggregation;
|
|
|
import org.elasticsearch.search.aggregations.AggregationBuilders;
|
|
|
import org.elasticsearch.search.aggregations.bucket.terms.Terms;
|
|
|
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
|
|
|
import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket;
|
|
|
import org.elasticsearch.search.aggregations.bucket.terms.TermsBuilder;
|
|
|
import org.elasticsearch.search.aggregations.metrics.tophits.TopHits;
|
|
|
import org.elasticsearch.search.sort.SortBuilder;
|
|
|
import org.elasticsearch.search.sort.SortBuilders;
|
|
|
import org.elasticsearch.search.sort.SortOrder;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.yoho.search.base.utils.ISearchConstants;
|
|
|
import com.yoho.search.core.es.model.SearchParam;
|
|
|
import com.yoho.search.core.es.model.SearchResult;
|
|
|
import com.yoho.search.service.cache.CacheEnum;
|
|
|
import com.yoho.search.service.personalized.PersonalVectorFeatureSearch;
|
|
|
import com.yoho.search.service.service.SearchCacheService;
|
|
|
import com.yoho.search.service.service.SearchCommonService;
|
|
|
import com.yoho.search.service.service.helper.AggProductListHelper;
|
|
|
import com.yoho.search.service.service.helper.FunctionScoreSearchHelper;
|
|
|
import com.yoho.search.service.service.helper.SearchCommonHelper;
|
|
|
import com.yoho.search.service.service.helper.SearchServiceHelper;
|
|
|
import com.yoho.search.service.service.helper.SearchSortHelper;
|
|
|
import com.yoho.search.service.servicenew.IGoodProductsService;
|
|
|
import com.yoho.search.service.utils.SearchRequestParams;
|
|
|
import com.yoho.search.service.vo.SearchApiResult;
|
|
|
|
|
|
@Service
|
...
|
...
|
@@ -27,65 +51,241 @@ public class GoodProductListService implements IGoodProductsService { |
|
|
|
|
|
@Autowired
|
|
|
private SearchCommonService searchCommonService;
|
|
|
@Autowired
|
|
|
private SearchServiceHelper searchServiceHelper;
|
|
|
@Autowired
|
|
|
private FunctionScoreSearchHelper functionScoreSearchHelper;
|
|
|
@Autowired
|
|
|
private SearchCacheService searchCacheService;
|
|
|
@Autowired
|
|
|
private AggProductListHelper aggProductListHelper;
|
|
|
@Autowired
|
|
|
private SearchSortHelper searchSortHelper;
|
|
|
@Autowired
|
|
|
private SearchCommonHelper searchCommonHelper;
|
|
|
@Autowired
|
|
|
private PersonalVectorFeatureSearch personalVectorFeatureSearch;
|
|
|
|
|
|
private final int maxSmallSortCount = 20;
|
|
|
private final int maxProductSknCountPerSort = 5;
|
|
|
|
|
|
@Override
|
|
|
public SearchApiResult goodProductList(Map<String, String> paramMap) {
|
|
|
//String productSkns = paramMap.get(SearchRequestParams.PARAM_SYNC_SKN);
|
|
|
BoolQueryBuilder boolFilter = QueryBuilders.boolQuery();
|
|
|
boolFilter.must(QueryBuilders.termQuery("isPhraseExist","Y"));
|
|
|
boolFilter.must(QueryBuilders.termQuery("productSkn", "50002052"));
|
|
|
// 1、检测分页参数
|
|
|
int pageSize = StringUtils.isBlank(paramMap.get("viewNum")) ? 30 : Integer.parseInt(paramMap.get("viewNum"));
|
|
|
int page = StringUtils.isBlank(paramMap.get("page")) ? 1 : Integer.parseInt(paramMap.get("page"));
|
|
|
if (page < 1 || pageSize < 0) {
|
|
|
return new SearchApiResult().setCode(400).setMessage("分页参数不合法");
|
|
|
}
|
|
|
// 2、先获取用户浏览的SKN对应的品类列表
|
|
|
List<Integer> smallSortIds = this.getProductSknSmallSortIds(paramMap, maxSmallSortCount);
|
|
|
// 3、再每个品类下获取5个SKN
|
|
|
List<String> recommondSkns = this.getRecommondedSkns(smallSortIds, maxProductSknCountPerSort, paramMap);
|
|
|
|
|
|
// 4、构造搜索参数
|
|
|
SearchParam searchParam = new SearchParam();
|
|
|
searchParam.setFiter(boolFilter);
|
|
|
searchCommonService.doSearch("productindex", searchParam);
|
|
|
return null;
|
|
|
searchParam.setFiter(this.getDefaultBoolQueryBuilder());
|
|
|
searchParam.setQuery(this.builderGoodProductQueryBuilder(paramMap, recommondSkns));
|
|
|
searchParam.setAggregationBuilders(null);
|
|
|
searchParam.setPage(page);
|
|
|
searchParam.setOffset((page - 1) * pageSize);
|
|
|
searchParam.setSize(pageSize);
|
|
|
List<SortBuilder> sortBuilders = new ArrayList<SortBuilder>();
|
|
|
sortBuilders.add(SortBuilders.fieldSort("_score").order(SortOrder.DESC));
|
|
|
sortBuilders.add(SortBuilders.fieldSort("salesNum").order(SortOrder.DESC));
|
|
|
sortBuilders.add(SortBuilders.fieldSort("firstShelveTime").order(SortOrder.DESC));
|
|
|
sortBuilders.add(SortBuilders.fieldSort("id").order(SortOrder.DESC));
|
|
|
searchParam.setSortBuilders(sortBuilders);
|
|
|
|
|
|
// 5)从缓存中获取数据
|
|
|
final String indexName = ISearchConstants.INDEX_NAME_PRODUCT_INDEX;
|
|
|
CacheEnum cacheEnum = CacheEnum.EHCACHE;
|
|
|
JSONObject cacheObject = searchCacheService.getJSONObjectFromCache(cacheEnum, indexName, searchParam);
|
|
|
if (cacheObject != null) {
|
|
|
return new SearchApiResult().setData(cacheObject);
|
|
|
}
|
|
|
// 6)查询ES
|
|
|
SearchResult searchResult = searchCommonService.doSearch(indexName, searchParam);
|
|
|
if (searchResult == null) {
|
|
|
return new SearchApiResult().setCode(500).setMessage("execption");
|
|
|
}
|
|
|
// 7)构造返回结果
|
|
|
JSONObject dataMap = new JSONObject();
|
|
|
dataMap.put("total", searchResult.getTotal());
|
|
|
dataMap.put("page", searchResult.getPage());
|
|
|
dataMap.put("page_size", searchParam.getSize());
|
|
|
dataMap.put("page_total", searchResult.getTotalPage());
|
|
|
dataMap.put("product_list", searchServiceHelper.getProductMapList(searchResult.getResultList()));
|
|
|
|
|
|
// 8)将结果存进缓存
|
|
|
searchCacheService.addJSONObjectToCache(cacheEnum, indexName, searchParam, dataMap);
|
|
|
return new SearchApiResult().setData(dataMap);
|
|
|
}
|
|
|
|
|
|
private QueryBuilder builderGoodProductQueryBuilder(Map<String, String> paramMap, List<String> recommondSkns) {
|
|
|
QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
|
|
|
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(queryBuilder);
|
|
|
// 针对参数里第一个SKN加分
|
|
|
String productSkns = paramMap.get(SearchRequestParams.PARAM_SYNC_SKN);
|
|
|
if (!StringUtils.isBlank(productSkns)) {
|
|
|
functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn", productSkns.split(",")[0]), ScoreFunctionBuilders.weightFactorFunction(100));
|
|
|
}
|
|
|
// 针对推荐出来的SKN做加分
|
|
|
if (recommondSkns != null && !recommondSkns.isEmpty()) {
|
|
|
functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn", recommondSkns), ScoreFunctionBuilders.weightFactorFunction(50));
|
|
|
}
|
|
|
// 加上个性化打分
|
|
|
if (searchCommonHelper.isNeedPersonalSearch(paramMap)) {
|
|
|
personalVectorFeatureSearch.addPersonalizedScriptScore(functionScoreQueryBuilder, paramMap);
|
|
|
}
|
|
|
return functionScoreQueryBuilder;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 获取skn的小物理分类列表
|
|
|
* 获取SKN相关的小分类
|
|
|
*
|
|
|
* @param productSkns
|
|
|
* @return
|
|
|
*/
|
|
|
private List<String> genProductSknSmallSortIds(String productSkns) {
|
|
|
private List<Integer> getProductSknSmallSortIds(Map<String, String> paramMap, int maxCount) {
|
|
|
String productSkns = paramMap.get(SearchRequestParams.PARAM_SYNC_SKN);
|
|
|
if (StringUtils.isBlank(productSkns)) {
|
|
|
return new ArrayList<Integer>();
|
|
|
}
|
|
|
String[] productSknArray = productSkns.split(",");
|
|
|
|
|
|
SearchParam searchParam = new SearchParam();
|
|
|
|
|
|
// 1、设置过滤条件
|
|
|
BoolQueryBuilder boolFilter = QueryBuilders.boolQuery();
|
|
|
boolFilter.must(QueryBuilders.termsQuery("productSkn", productSknArray));
|
|
|
searchParam.setFiter(boolFilter);
|
|
|
// 2、设置聚合条件
|
|
|
final String aggName = "smallSortAgg";
|
|
|
TermsBuilder smallSortIdAgg = AggregationBuilders.terms(aggName).field("smallSortId").size(maxCount);
|
|
|
searchParam.setAggregationBuilders(Arrays.asList(smallSortIdAgg));
|
|
|
// 3、设置分页
|
|
|
searchParam.setPage(0);
|
|
|
searchParam.setSize(0);
|
|
|
searchParam.setOffset(0);
|
|
|
// 4、执行搜索
|
|
|
SearchResult searchResult = searchCommonService.doSearch(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
|
|
|
if (searchResult == null || searchResult.getAggMaps() == null || searchResult.getAggMaps().get("smallSortAgg") == null) {
|
|
|
return new ArrayList<Integer>();
|
|
|
}
|
|
|
List<Integer> results = new ArrayList<Integer>();
|
|
|
MultiBucketsAggregation aggregation = (MultiBucketsAggregation) searchResult.getAggMaps().get(aggName);
|
|
|
Iterator<? extends Bucket> smallSortIdIterator = aggregation.getBuckets().iterator();
|
|
|
while (smallSortIdIterator.hasNext()) {
|
|
|
Bucket smallSortIdBucket = smallSortIdIterator.next();
|
|
|
if (StringUtils.isNumeric(smallSortIdBucket.getKeyAsString())) {
|
|
|
results.add(Integer.valueOf(smallSortIdBucket.getKeyAsString()));
|
|
|
}
|
|
|
}
|
|
|
return results;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 有好货默认的过滤规则
|
|
|
*
|
|
|
* @return
|
|
|
*/
|
|
|
private BoolQueryBuilder getDefaultBoolQueryBuilder() {
|
|
|
BoolQueryBuilder boolFilter = QueryBuilders.boolQuery();
|
|
|
boolFilter.mustNot(QueryBuilders.termsQuery("isSeckill", "Y"));
|
|
|
boolFilter.mustNot(QueryBuilders.termsQuery("isGlobal", "Y"));
|
|
|
boolFilter.must(QueryBuilders.termQuery("status", 1));
|
|
|
boolFilter.must(QueryBuilders.termQuery("isOutlets", 2));
|
|
|
boolFilter.must(QueryBuilders.termQuery("attribute", 1));
|
|
|
boolFilter.must(QueryBuilders.rangeQuery("storageNum").gte(1));
|
|
|
boolFilter.must(QueryBuilders.termsQuery("productSkn", productSknArray));
|
|
|
searchParam.setFiter(boolFilter);
|
|
|
// 默认推库存>2,非断码,并且短评存在的数据
|
|
|
boolFilter.must(QueryBuilders.rangeQuery("storageNum").gte(2));
|
|
|
boolFilter.must(QueryBuilders.rangeQuery("breakingRate").lt(50));
|
|
|
boolFilter.must(QueryBuilders.rangeQuery("basePinRatio").lt(3.5));
|
|
|
boolFilter.must(QueryBuilders.termQuery("isPhraseExist", "Y"));
|
|
|
return boolFilter;
|
|
|
}
|
|
|
|
|
|
// 2、设置聚合条件
|
|
|
private List<String> getRecommondedSkns(List<Integer> smallSortIds, int maxSize, Map<String, String> paramMap) {
|
|
|
// 1、如果品类为空,则直接返回
|
|
|
if (smallSortIds == null || smallSortIds.isEmpty()) {
|
|
|
return new ArrayList<String>();
|
|
|
}
|
|
|
SearchParam searchParam = new SearchParam();
|
|
|
|
|
|
// 2、构造filter
|
|
|
BoolQueryBuilder boolFilter = this.getDefaultBoolQueryBuilder();
|
|
|
boolFilter.must(QueryBuilders.termsQuery("smallSortId", smallSortIds));
|
|
|
|
|
|
// 3、构造query[针对用户做个性化打分]
|
|
|
searchParam.setQuery(searchServiceHelper.constructQueryBuilderForProductList(paramMap));
|
|
|
|
|
|
// 4、设置聚合条件
|
|
|
final String firstAggName = "firstAgg";
|
|
|
String order = "_score:desc";
|
|
|
String sortField = order.split(":")[0];
|
|
|
SortOrder sortOrder = order.split(":")[1].equals("desc") ? SortOrder.DESC : SortOrder.ASC;
|
|
|
List<AbstractAggregationBuilder> list = new ArrayList<AbstractAggregationBuilder>();
|
|
|
// 3.1)构造父聚合:品牌或品类聚合【同时按子聚合的sort字段排序】
|
|
|
TermsBuilder brandAggregationBuilder = AggregationBuilders.terms(firstAggName).field("smallSortId").order(Terms.Order.aggregation("sort", sortOrder.equals(SortOrder.ASC)))
|
|
|
.size(200 + productSknArray.length);
|
|
|
// 3.2)添加子聚合:取得分最大的值
|
|
|
brandAggregationBuilder.subAggregation(AggregationBuilders.max("sort").field(sortField));
|
|
|
// 3.3)添加孙聚合:取打分最高的一个product
|
|
|
brandAggregationBuilder.subAggregation(AggregationBuilders.topHits("product").addSort(SortBuilders.fieldSort(sortField).order(sortOrder)).setSize(2));
|
|
|
list.add(brandAggregationBuilder);
|
|
|
// 4.1)构造父聚合:品牌或品类聚合【同时按子聚合的sort字段排序】
|
|
|
TermsBuilder parentAggregationBuilder = AggregationBuilders.terms(firstAggName).field("smallSortId").size(smallSortIds.size());
|
|
|
// 4.2)添加子聚合:取得分最大的值
|
|
|
parentAggregationBuilder.subAggregation(AggregationBuilders.max("sort").field(sortField));
|
|
|
// 4.3)添加孙聚合:取打分最高的一个product
|
|
|
parentAggregationBuilder.subAggregation(AggregationBuilders.topHits("product").addSort(SortBuilders.fieldSort(sortField).order(sortOrder)).setSize(maxSize));
|
|
|
list.add(parentAggregationBuilder);
|
|
|
searchParam.setAggregationBuilders(list);
|
|
|
|
|
|
// 3、设置分页
|
|
|
// 5、设置分页
|
|
|
searchParam.setPage(0);
|
|
|
searchParam.setSize(0);
|
|
|
searchParam.setOffset(0);
|
|
|
|
|
|
//4、执行搜索
|
|
|
SearchResult searchResult = searchCommonService.doSearch(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
|
|
|
return null;
|
|
|
|
|
|
// 6、先从缓存中获取,如果能取到,则直接返回
|
|
|
JSONArray recommendedSknJSONArray = searchCacheService.getJSONArrayFromCache(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
|
|
|
if (recommendedSknJSONArray != null) {
|
|
|
return this.jsonArrayToList(recommendedSknJSONArray);
|
|
|
}
|
|
|
// 7、执行搜索,并构造返回结果
|
|
|
final String indexName = ISearchConstants.INDEX_NAME_PRODUCT_INDEX;
|
|
|
SearchResult searchResult = searchCommonService.doSearch(indexName, searchParam);
|
|
|
if (searchResult == null || searchResult.getAggMaps() == null) {
|
|
|
return new ArrayList<String>();
|
|
|
}
|
|
|
Map<String, Aggregation> aggMaps = searchResult.getAggMaps();
|
|
|
if (!aggMaps.containsKey(firstAggName)) {
|
|
|
return new ArrayList<String>();
|
|
|
}
|
|
|
List<String> recommendedSknList = this.getRecommendedSknList((MultiBucketsAggregation) aggMaps.get(firstAggName));
|
|
|
recommendedSknJSONArray = new JSONArray();
|
|
|
for (String recommendedSkn : recommendedSknList) {
|
|
|
recommendedSknJSONArray.add(recommendedSkn);
|
|
|
}
|
|
|
searchCacheService.addJSONArrayToCache(indexName, searchParam, recommendedSknJSONArray);
|
|
|
return this.jsonArrayToList(recommendedSknJSONArray);
|
|
|
}
|
|
|
|
|
|
private List<String> jsonArrayToList(JSONArray jsonArray) {
|
|
|
List<String> results = new ArrayList<String>();
|
|
|
for (int i = 0; i < jsonArray.size(); i++) {
|
|
|
results.add(jsonArray.getString(i));
|
|
|
}
|
|
|
return results;
|
|
|
}
|
|
|
|
|
|
private List<String> getRecommendedSknList(MultiBucketsAggregation aggregation) {
|
|
|
Iterator<? extends Bucket> itAgg = aggregation.getBuckets().iterator();
|
|
|
// 获取聚合出来的商品
|
|
|
List<String> recommendedSknList = new ArrayList<String>();
|
|
|
while (itAgg.hasNext()) {
|
|
|
Bucket lt = itAgg.next();
|
|
|
if (lt.getAggregations().getAsMap().containsKey("product")) {
|
|
|
TopHits topHits = lt.getAggregations().get("product");
|
|
|
if (topHits != null) {
|
|
|
SearchHits hits = topHits.getHits();
|
|
|
for (SearchHit hit : hits.getHits()) {
|
|
|
recommendedSknList.add("" + hit.getSource().get("productSkn"));
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
Collections.shuffle(recommendedSknList);
|
|
|
return recommendedSknList;
|
|
|
}
|
|
|
} |
...
|
...
|
|