Authored by Gino Zhang

Merge branch 'master' into zf_smart_search3

# Conflicts:
#	service/src/main/java/com/yoho/search/service/restapi/ProductListController.java
... ... @@ -23,7 +23,7 @@ public class PersonalVectorFeatureSearch {
private static final Double BASE_CONSTANT = 1.0D;
private static final Double FACTOR_CONSTANT = 1.0D;
private static final Double FACTOR_CONSTANT = 0.8D;
@Autowired
private SearchDynamicConfigService searchDynamicConfigService;
... ...
package com.yoho.search.service.restapi;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.yoho.search.service.servicenew.IGoodProductsService;
import com.yoho.search.service.utils.HttpServletRequestUtils;
import com.yoho.search.service.vo.SearchApiResult;
@Controller
public class GoodProductListController {
@Autowired
private IGoodProductsService goodProductsService;
@RequestMapping(method = RequestMethod.GET, value = "/productindex/goodProductList")
@ResponseBody
public SearchApiResult goodProductList(HttpServletRequest request) {
Map<String, String> paramMap = HttpServletRequestUtils.transParamType(request);
return goodProductsService.goodProductList(paramMap);
}
}
... ...
package com.yoho.search.service.service.helper;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Map;
... ... @@ -17,10 +18,12 @@ import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import com.yoho.search.base.utils.DateUtil;
import com.yoho.search.service.personalized.PersonalVectorFeatureSearch;
import com.yoho.search.service.personalized.PersonalizedRedisService;
import com.yoho.search.service.service.SearchDynamicConfigService;
import com.yoho.search.service.utils.SearchRequestParams;
import com.yoho.search.service.vo.FirstShelveTimeScore;
import com.yoho.search.service.vo.PhysicalChannelScore;
@Component
... ... @@ -37,7 +40,12 @@ public class FunctionScoreSearchHelper {
@Autowired
private PersonalizedRedisService personalizedRedisService;
static float globalWeight = 0.50f;
// 普通个性化的时间维度
private final int oneDaySecondCount = 24 * 60 * 60;
private FirstShelveTimeScore commonFirstShelveTimeScore = new FirstShelveTimeScore(90,30,60);
// 新品到着的个性化时间维度
private FirstShelveTimeScore newRecShelveTimeScore = new FirstShelveTimeScore(30,10,20);
private final float globalWeight = 0.50f;
private WeightBuilder genWeightFactorBuilder(float factor) {
return ScoreFunctionBuilders.weightFactorFunction(factor);
... ... @@ -48,29 +56,57 @@ public class FunctionScoreSearchHelper {
// 将某些SKN展示到前面
if (searchCommonHelper.isFirstProductSknSearch(paramMap)) {
String[] productSkns = paramMap.get(SearchRequestParams.FIRST_PRODUCRSKN).split(",");
functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn",productSkns),ScoreFunctionBuilders.weightFactorFunction(1000));
functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn", productSkns), ScoreFunctionBuilders.weightFactorFunction(1000));
}
// 个性化搜索相关
if (searchCommonHelper.isNeedPersonalSearch(paramMap)) {
personalVectorFeatureSearch.addPersonalizedScriptScore(functionScoreQueryBuilder, paramMap);
this.addFirstShelveTimeFunctionScore(functionScoreQueryBuilder, paramMap);
}
// 针对全球购降分
if (searchCommonHelper.containGlobal(paramMap)) {
functionScoreQueryBuilder.add(QueryBuilders.termQuery("isGlobal", "Y"), genWeightFactorBuilder(globalWeight));
}
if (searchCommonHelper.isNeedDeScoreBrandSearch(paramMap)) {
functionScoreQueryBuilder.add(QueryBuilders.termQuery("isForbiddenSortBrand", "1"), ScoreFunctionBuilders.weightFactorFunction(0));
}
// 针对频道降分
// 模糊搜索针对频道降分
if (searchCommonHelper.isNeedDeScoreForChannel(paramMap)) {
List<PhysicalChannelScore> physicalChannelScores = this.getPhysicalChannelQueryBuilder(paramMap);
for (PhysicalChannelScore physicalChannelScore : physicalChannelScores) {
functionScoreQueryBuilder.add(physicalChannelScore.getQueryBuilder(), ScoreFunctionBuilders.weightFactorFunction(physicalChannelScore.getWeight()));
}
}
// 针对屏蔽的品牌降分【目前没用】
if (searchCommonHelper.isNeedDeScoreBrandSearch(paramMap)) {
functionScoreQueryBuilder.add(QueryBuilders.termQuery("isForbiddenSortBrand", "1"), ScoreFunctionBuilders.weightFactorFunction(0));
}
functionScoreQueryBuilder.boostMode(CombineFunction.MULT);
return functionScoreQueryBuilder;
}
/**
* 个性化时添加时间维度的降分
*
* @param functionScoreQueryBuilder
* @param paramMap
*/
private void addFirstShelveTimeFunctionScore(FunctionScoreQueryBuilder functionScoreQueryBuilder, Map<String, String> paramMap) {
if (searchCommonHelper.isNewRecPageDefault(paramMap)) {
this.addFirstShelveTimeScore(functionScoreQueryBuilder, newRecShelveTimeScore);
} else {
this.addFirstShelveTimeScore(functionScoreQueryBuilder, commonFirstShelveTimeScore);
}
}
private void addFirstShelveTimeScore(FunctionScoreQueryBuilder functionScoreQueryBuilder, FirstShelveTimeScore firstShelveTimeScore) {
int todayLastSecond = DateUtil.getLastTimeSecond(new Date());
int limitSecondValue = todayLastSecond - firstShelveTimeScore.getLimitDayCount() * oneDaySecondCount;
int scaleSecondTime = firstShelveTimeScore.getScaleDayCount() * oneDaySecondCount;
int offsetSecondValue = firstShelveTimeScore.getOffsetDayCount() * oneDaySecondCount;
functionScoreQueryBuilder.add(QueryBuilders.rangeQuery("firstShelveTime").lt(limitSecondValue), ScoreFunctionBuilders.weightFactorFunction(0.5f));
functionScoreQueryBuilder.add(QueryBuilders.rangeQuery("firstShelveTime").from(limitSecondValue),
ScoreFunctionBuilders.linearDecayFunction("firstShelveTime", todayLastSecond, scaleSecondTime).setOffset(offsetSecondValue));
}
/**
* 直接使用商品特征的向量用来做个性化打分
*
* @param queryBuilder
... ... @@ -91,7 +127,7 @@ public class FunctionScoreSearchHelper {
* @param descoreGender
* @return
*/
public float getDescoreGenderWeight(String uid, float baseScore, String descoreGender) {
private float getDescoreGenderWeight(String uid, float baseScore, String descoreGender) {
try {
Map<String, Float> userGenderFloat = personalizedRedisService.getUserGenderFeature(uid);
Float userGenderWeight = userGenderFloat.get(descoreGender);
... ... @@ -116,7 +152,7 @@ public class FunctionScoreSearchHelper {
// 潮童频道,对非潮童频道的和成人的商品降分
if (physicalChannel.equals("3")) {
results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder,physicalChannelWeight));
results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder, physicalChannelWeight));
results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("ageLevel", "1"), physicalChannelWeight));
return results;
}
... ... @@ -132,14 +168,14 @@ public class FunctionScoreSearchHelper {
}
// 男生频道,对非男生频道的商品和性别女的降分
if (physicalChannel.equals("1")) {
results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder,physicalChannelWeight));
results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder, physicalChannelWeight));
results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("gender", "2"), this.getDescoreGenderWeight(uid, physicalChannelWeight, "2")));
return results;
}
// 女生频道,对非女生频道的商品和性别男的降分
if (physicalChannel.equals("2")) {
results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder,physicalChannelWeight));
results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("gender", "1"),this.getDescoreGenderWeight(uid, physicalChannelWeight, "1")));
results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder, physicalChannelWeight));
results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("gender", "1"), this.getDescoreGenderWeight(uid, physicalChannelWeight, "1")));
return results;
}
return new ArrayList<PhysicalChannelScore>();
... ...
... ... @@ -108,7 +108,7 @@ public class SearchCommonHelper {
}
return false;
}
/**
* 是否需要对频道降分
*
... ... @@ -117,13 +117,13 @@ public class SearchCommonHelper {
*/
public boolean isNeedDeScoreForChannel(Map<String, String> paramMap) {
String physicalChannel = paramMap.get(SearchRequestParams.PHYSICAL_CHANNEL);
if(StringUtils.isBlank(physicalChannel)){
if (StringUtils.isBlank(physicalChannel)) {
return false;
}
if(!isFuzzySearchDefault(paramMap)){
if (!isFuzzySearchDefault(paramMap)) {
return false;
}
if(!dynamicConfig.isDeScorePhysicalChannelOpen()){
if (!dynamicConfig.isDeScorePhysicalChannelOpen()) {
return false;
}
return true;
... ... @@ -187,6 +187,25 @@ public class SearchCommonHelper {
}
/**
* 是否是新品到着默认页
*
* @param paramMap
* @return
*/
public boolean isNewRecPageDefault(Map<String, String> paramMap) {
String pageId = paramMap.get("pageId");
if (StringUtils.isBlank(pageId)|| !pageId.equals("4")){
return false;
}
String order = paramMap.get("order");
if (!StringUtils.isBlank(order)) {
return false;
}
return true;
}
/**
* 关键字中含性别,则加上性别的过滤条件
*
* @param keyword
... ...
... ... @@ -156,23 +156,6 @@ public class SearchServiceHelper {
QueryBuilder queryBuilder = this.constructQueryBuilder(paramMap);
queryBuilder = functionScoreSearchHelper.buildFunctionScoreQueryBuild(queryBuilder, paramMap);
return queryBuilder;
// queryBuilder = this.buildPersonalSearch(queryBuilder, paramMap);
// queryBuilder = this.buildFunctionScoreQueryBuild(queryBuilder,
// paramMap);
// return queryBuilder;
// String dynamicRuleValue =
// dynamicSearchRuleHelper.getDynamicRuleValue(paramMap);
// if (StringUtils.isEmpty(dynamicRuleValue) ||
// "-1".equals(dynamicRuleValue)) {
// queryBuilder = this.buildGlobalSearch(queryBuilder, paramMap);
// queryBuilder = this.buildDeScoreBrandSearch(queryBuilder, paramMap);
// } else {
// queryBuilder =
// dynamicSearchRuleHelper.buildDynamicSerach(queryBuilder, paramMap,
// dynamicRuleValue);
// }
// return queryBuilder;
}
public QueryBuilder constructOrQueryBuilderForProductList(Map<String, String> paramMap) {
... ... @@ -649,13 +632,17 @@ public class SearchServiceHelper {
}
}
public List<Map<String, Object>> getProductMapList(List<Map<String, Object>> resultList) {
return this.getProductMapList(resultList, null);
}
/**
* 获取商品列表返回结果
*
* @param resultList
* @return
*/
public List<Map<String, Object>> getProductMapList(List<Map<String, Object>> resultList) {
public List<Map<String, Object>> getProductMapList(List<Map<String, Object>> resultList, List<String> extendedFields) {
// 获取搜索结果的skn,根据它们来获取product_price_plan
String[] sknStr = new String[resultList.size()];
for (int i = 0; i < resultList.size(); i++) {
... ... @@ -664,7 +651,7 @@ public class SearchServiceHelper {
List<Map<String, Object>> pageList = new ArrayList<Map<String, Object>>();
Map<String, List<Map<String, Object>>> productPricePlanMap = this.searchProductPricePlan(sknStr);
for (Map<String, Object> map : resultList) {
Map<String, Object> productMap = getProductMap(map);
Map<String, Object> productMap = getProductMap(map, extendedFields);
productMap.put("product_price_plan_list", productPricePlanMap.get("" + map.get("productSkn")));
pageList.add(productMap);
}
... ... @@ -677,8 +664,12 @@ public class SearchServiceHelper {
return result;
}
@SuppressWarnings("unchecked")
public Map<String, Object> getProductMap(Map<String, Object> map) {
return this.getProductMap(map, null);
}
@SuppressWarnings("unchecked")
public Map<String, Object> getProductMap(Map<String, Object> map, List<String> extendedFields) {
Map<String, Object> productMap = new HashMap<String, Object>();
productMap.put("cn_alphabet", map.get("cnAlphabet") == null ? "" : map.get("cnAlphabet"));
if (map.containsKey("_highlight") && map.get("_highlight") != null) {
... ... @@ -727,7 +718,6 @@ public class SearchServiceHelper {
productMap.put("sales_num", map.get("salesNum"));
productMap.put("status", map.get("status"));
productMap.put("is_promotion", map.get("ispromotion"));
productMap.put("is_promotion", map.get("ispromotion"));
String yohoodIdFromMap = (String) map.get("yohoodId");
if (yohoodIdFromMap != null && yohoodIdFromMap.length() > 0) {
productMap.put("yohood_id", yohoodIdFromMap);
... ... @@ -761,6 +751,12 @@ public class SearchServiceHelper {
String tbl_plane = (tbl_country_id != null && tbl_country_id != 86) ? "Y" : "N";
productMap.put("tbl_plane", tbl_plane);
productMap.put("skn_default_img", map.get("sknDefaultImg"));
// 一些特殊场景需要额外返回一些参数
if (extendedFields != null) {
for (String extendedField : extendedFields) {
productMap.put(extendedField, map.get(extendedField));
}
}
return productMap;
}
}
... ...
package com.yoho.search.service.servicenew;
import java.util.Map;
import com.yoho.search.service.vo.SearchApiResult;
public interface IGoodProductsService {
/**
* 有好货商品列表接口
* @param paramMap
* @return
*/
public SearchApiResult goodProductList(Map<String, String> paramMap);
}
... ...
package com.yoho.search.service.servicenew.impl;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
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.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
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;
private final int maxCountPerGroup = 10;
private final float firstSknScore = 300;
private final float recommendedSknMaxScore = 200;
@Override
public SearchApiResult goodProductList(Map<String, String> paramMap) {
// 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(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(), Arrays.asList("phrase")));
// 8)将结果存进缓存
searchCacheService.addJSONObjectToCache(cacheEnum, indexName, searchParam, dataMap);
return new SearchApiResult().setData(dataMap);
}
private QueryBuilder builderGoodProductQueryBuilder(Map<String, String> paramMap, List<String> recommendedSknList) {
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(firstSknScore));
}
// 针对推荐出来的SKN做加分
if (recommendedSknList != null && !recommendedSknList.isEmpty()) {
Map<Integer, List<String>> recommondSknMap = this.splitProductSkns(recommendedSknList, maxCountPerGroup);
float currentGroupScore = recommendedSknMaxScore;
for (Map.Entry<Integer, List<String>> entry: recommondSknMap.entrySet()) {
functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn", entry.getValue()), ScoreFunctionBuilders.weightFactorFunction(currentGroupScore));
currentGroupScore = currentGroupScore - 10;
}
}
// 加上个性化打分
if (searchCommonHelper.isNeedPersonalSearch(paramMap)) {
personalVectorFeatureSearch.addPersonalizedScriptScore(functionScoreQueryBuilder, paramMap);
}
return functionScoreQueryBuilder;
}
/**
* 获取SKN相关的小分类
*
* @param productSkns
* @return
*/
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、先从缓存中获取,如果能取到,则直接返回
JSONArray sknSmallSortIdJSONArray = searchCacheService.getJSONArrayFromCache(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
if (sknSmallSortIdJSONArray != null) {
return this.jsonArrayToList(sknSmallSortIdJSONArray, Integer.class);
}
// 5、执行搜索
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>();
}
sknSmallSortIdJSONArray = new JSONArray();
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())) {
sknSmallSortIdJSONArray.add(Integer.valueOf(smallSortIdBucket.getKeyAsString()));
}
}
searchCacheService.addJSONArrayToCache(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam, sknSmallSortIdJSONArray);
return this.jsonArrayToList(sknSmallSortIdJSONArray, Integer.class);
}
/**
* 有好货默认的过滤规则
*
* @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));
// 默认推库存>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;
}
/**
* 每个品类为用户推荐maxSize个SKN
*
* @param smallSortIds
* @param maxSize
* @param paramMap
* @return
*/
private List<String> getRecommondedSkns(List<Integer> smallSortIds, int maxSizePerSort, 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));
searchParam.setFiter(boolFilter);
// 3、构造query
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(QueryBuilders.matchAllQuery());
// 针对看过的SKN做加分
String productSkns = paramMap.get(SearchRequestParams.PARAM_SYNC_SKN);
if (!StringUtils.isBlank(productSkns)) {
functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn", productSkns.split(",")), ScoreFunctionBuilders.weightFactorFunction(100));
}
// 加上个性化打分
if (searchCommonHelper.isNeedPersonalSearch(paramMap)) {
personalVectorFeatureSearch.addPersonalizedScriptScore(functionScoreQueryBuilder, paramMap);
}
searchParam.setQuery(functionScoreQueryBuilder);
// 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>();
// 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(maxSizePerSort));
list.add(parentAggregationBuilder);
searchParam.setAggregationBuilders(list);
// 5、设置分页
searchParam.setPage(0);
searchParam.setSize(0);
searchParam.setOffset(0);
// 6、先从缓存中获取,如果能取到,则直接返回
JSONArray recommendedSknJSONArray = searchCacheService.getJSONArrayFromCache(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
if (recommendedSknJSONArray != null) {
return this.jsonArrayToList(recommendedSknJSONArray, String.class);
}
// 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, String.class);
}
private <T> List<T> jsonArrayToList(JSONArray jsonArray, Class<T> clazz) {
return JSONObject.parseArray(jsonArray.toJSONString(), clazz);
}
/**
* 分组
*
* @param recommendedSknList
* @param maxGroupCount
* @return
*/
private Map<Integer, List<String>> splitProductSkns(List<String> recommendedSknList, int maxCountPerGroup) {
int maxSize = recommendedSknList.size();
int groupSize = maxSize / maxCountPerGroup;
if (maxSize % maxCountPerGroup > 0) {
groupSize = groupSize + 1;
}
Map<Integer, List<String>> result = new HashMap<Integer, List<String>>();
for (int i = 0; i < groupSize; i++) {
int fromIndex = i * maxCountPerGroup;
int toIndex = (i + 1) * maxCountPerGroup;
result.put(i, recommendedSknList.subList(fromIndex, toIndex > maxSize ? maxSize : toIndex));
}
return result;
}
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;
}
public static void main(String[] args) {
List<String> list = new ArrayList<String>();
for (int i = 1; i <= 99; i++) {
list.add(i+"");
}
Map<Integer, List<String>> results = new GoodProductListService().splitProductSkns(list, 20);
for (Map.Entry<Integer, List<String>> entry : results.entrySet()) {
System.out.println(entry.getKey() + "_" + entry.getValue());
}
}
}
... ...
... ... @@ -197,7 +197,7 @@ public class PromotionServiceImpl implements IPromotionService {
public SearchApiResult list(PromotionConditions promotionConditions, Map<String, String> paramMap) throws Exception {
SearchParam searchParam = new SearchParam();
// 1、设置查询条件
searchParam.setQuery(searchServiceHelper.constructQueryBuilder(paramMap));
searchParam.setQuery(searchServiceHelper.constructQueryBuilderForProductList(paramMap));
// 2、设置过滤条件
BoolQueryBuilder boolQueryBuilder = searchServiceHelper.constructFilterBuilder(paramMap, null);
BoolQueryBuilder mustFilterByPromotion = this.getMustFilterByPromotion(promotionConditions);
... ...
package com.yoho.search.service.vo;
public class FirstShelveTimeScore {
private int limitDayCount;
private int offsetDayCount;
private int scaleDayCount;
public FirstShelveTimeScore(int limitDayCount, int offsetDayCount, int scaleDayCount) {
super();
this.limitDayCount = limitDayCount;
this.offsetDayCount = offsetDayCount;
this.scaleDayCount = scaleDayCount;
}
public int getLimitDayCount() {
return limitDayCount;
}
public int getOffsetDayCount() {
return offsetDayCount;
}
public int getScaleDayCount() {
return scaleDayCount;
}
}
... ...