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 { @@ -23,7 +23,7 @@ public class PersonalVectorFeatureSearch {
23 23
24 private static final Double BASE_CONSTANT = 1.0D; 24 private static final Double BASE_CONSTANT = 1.0D;
25 25
26 - private static final Double FACTOR_CONSTANT = 1.0D; 26 + private static final Double FACTOR_CONSTANT = 0.8D;
27 27
28 @Autowired 28 @Autowired
29 private SearchDynamicConfigService searchDynamicConfigService; 29 private SearchDynamicConfigService searchDynamicConfigService;
  1 +package com.yoho.search.service.restapi;
  2 +
  3 +import java.util.Map;
  4 +
  5 +import javax.servlet.http.HttpServletRequest;
  6 +
  7 +import org.springframework.beans.factory.annotation.Autowired;
  8 +import org.springframework.stereotype.Controller;
  9 +import org.springframework.web.bind.annotation.RequestMapping;
  10 +import org.springframework.web.bind.annotation.RequestMethod;
  11 +import org.springframework.web.bind.annotation.ResponseBody;
  12 +
  13 +import com.yoho.search.service.servicenew.IGoodProductsService;
  14 +import com.yoho.search.service.utils.HttpServletRequestUtils;
  15 +import com.yoho.search.service.vo.SearchApiResult;
  16 +
  17 +@Controller
  18 +public class GoodProductListController {
  19 +
  20 + @Autowired
  21 + private IGoodProductsService goodProductsService;
  22 +
  23 + @RequestMapping(method = RequestMethod.GET, value = "/productindex/goodProductList")
  24 + @ResponseBody
  25 + public SearchApiResult goodProductList(HttpServletRequest request) {
  26 + Map<String, String> paramMap = HttpServletRequestUtils.transParamType(request);
  27 + return goodProductsService.goodProductList(paramMap);
  28 + }
  29 +
  30 +}
1 package com.yoho.search.service.service.helper; 1 package com.yoho.search.service.service.helper;
2 2
3 import java.util.ArrayList; 3 import java.util.ArrayList;
  4 +import java.util.Date;
4 import java.util.List; 5 import java.util.List;
5 import java.util.Map; 6 import java.util.Map;
6 7
@@ -17,10 +18,12 @@ import org.slf4j.LoggerFactory; @@ -17,10 +18,12 @@ import org.slf4j.LoggerFactory;
17 import org.springframework.beans.factory.annotation.Autowired; 18 import org.springframework.beans.factory.annotation.Autowired;
18 import org.springframework.stereotype.Component; 19 import org.springframework.stereotype.Component;
19 20
  21 +import com.yoho.search.base.utils.DateUtil;
20 import com.yoho.search.service.personalized.PersonalVectorFeatureSearch; 22 import com.yoho.search.service.personalized.PersonalVectorFeatureSearch;
21 import com.yoho.search.service.personalized.PersonalizedRedisService; 23 import com.yoho.search.service.personalized.PersonalizedRedisService;
22 import com.yoho.search.service.service.SearchDynamicConfigService; 24 import com.yoho.search.service.service.SearchDynamicConfigService;
23 import com.yoho.search.service.utils.SearchRequestParams; 25 import com.yoho.search.service.utils.SearchRequestParams;
  26 +import com.yoho.search.service.vo.FirstShelveTimeScore;
24 import com.yoho.search.service.vo.PhysicalChannelScore; 27 import com.yoho.search.service.vo.PhysicalChannelScore;
25 28
26 @Component 29 @Component
@@ -37,7 +40,12 @@ public class FunctionScoreSearchHelper { @@ -37,7 +40,12 @@ public class FunctionScoreSearchHelper {
37 @Autowired 40 @Autowired
38 private PersonalizedRedisService personalizedRedisService; 41 private PersonalizedRedisService personalizedRedisService;
39 42
40 - static float globalWeight = 0.50f; 43 + // 普通个性化的时间维度
  44 + private final int oneDaySecondCount = 24 * 60 * 60;
  45 + private FirstShelveTimeScore commonFirstShelveTimeScore = new FirstShelveTimeScore(90,30,60);
  46 + // 新品到着的个性化时间维度
  47 + private FirstShelveTimeScore newRecShelveTimeScore = new FirstShelveTimeScore(30,10,20);
  48 + private final float globalWeight = 0.50f;
41 49
42 private WeightBuilder genWeightFactorBuilder(float factor) { 50 private WeightBuilder genWeightFactorBuilder(float factor) {
43 return ScoreFunctionBuilders.weightFactorFunction(factor); 51 return ScoreFunctionBuilders.weightFactorFunction(factor);
@@ -48,29 +56,57 @@ public class FunctionScoreSearchHelper { @@ -48,29 +56,57 @@ public class FunctionScoreSearchHelper {
48 // 将某些SKN展示到前面 56 // 将某些SKN展示到前面
49 if (searchCommonHelper.isFirstProductSknSearch(paramMap)) { 57 if (searchCommonHelper.isFirstProductSknSearch(paramMap)) {
50 String[] productSkns = paramMap.get(SearchRequestParams.FIRST_PRODUCRSKN).split(","); 58 String[] productSkns = paramMap.get(SearchRequestParams.FIRST_PRODUCRSKN).split(",");
51 - functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn",productSkns),ScoreFunctionBuilders.weightFactorFunction(1000)); 59 + functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn", productSkns), ScoreFunctionBuilders.weightFactorFunction(1000));
52 } 60 }
  61 + // 个性化搜索相关
53 if (searchCommonHelper.isNeedPersonalSearch(paramMap)) { 62 if (searchCommonHelper.isNeedPersonalSearch(paramMap)) {
54 personalVectorFeatureSearch.addPersonalizedScriptScore(functionScoreQueryBuilder, paramMap); 63 personalVectorFeatureSearch.addPersonalizedScriptScore(functionScoreQueryBuilder, paramMap);
  64 + this.addFirstShelveTimeFunctionScore(functionScoreQueryBuilder, paramMap);
55 } 65 }
  66 + // 针对全球购降分
56 if (searchCommonHelper.containGlobal(paramMap)) { 67 if (searchCommonHelper.containGlobal(paramMap)) {
57 functionScoreQueryBuilder.add(QueryBuilders.termQuery("isGlobal", "Y"), genWeightFactorBuilder(globalWeight)); 68 functionScoreQueryBuilder.add(QueryBuilders.termQuery("isGlobal", "Y"), genWeightFactorBuilder(globalWeight));
58 } 69 }
59 - if (searchCommonHelper.isNeedDeScoreBrandSearch(paramMap)) {  
60 - functionScoreQueryBuilder.add(QueryBuilders.termQuery("isForbiddenSortBrand", "1"), ScoreFunctionBuilders.weightFactorFunction(0));  
61 - }  
62 - // 针对频道降分 70 + // 模糊搜索针对频道降分
63 if (searchCommonHelper.isNeedDeScoreForChannel(paramMap)) { 71 if (searchCommonHelper.isNeedDeScoreForChannel(paramMap)) {
64 List<PhysicalChannelScore> physicalChannelScores = this.getPhysicalChannelQueryBuilder(paramMap); 72 List<PhysicalChannelScore> physicalChannelScores = this.getPhysicalChannelQueryBuilder(paramMap);
65 for (PhysicalChannelScore physicalChannelScore : physicalChannelScores) { 73 for (PhysicalChannelScore physicalChannelScore : physicalChannelScores) {
66 functionScoreQueryBuilder.add(physicalChannelScore.getQueryBuilder(), ScoreFunctionBuilders.weightFactorFunction(physicalChannelScore.getWeight())); 74 functionScoreQueryBuilder.add(physicalChannelScore.getQueryBuilder(), ScoreFunctionBuilders.weightFactorFunction(physicalChannelScore.getWeight()));
67 } 75 }
68 } 76 }
  77 + // 针对屏蔽的品牌降分【目前没用】
  78 + if (searchCommonHelper.isNeedDeScoreBrandSearch(paramMap)) {
  79 + functionScoreQueryBuilder.add(QueryBuilders.termQuery("isForbiddenSortBrand", "1"), ScoreFunctionBuilders.weightFactorFunction(0));
  80 + }
69 functionScoreQueryBuilder.boostMode(CombineFunction.MULT); 81 functionScoreQueryBuilder.boostMode(CombineFunction.MULT);
70 return functionScoreQueryBuilder; 82 return functionScoreQueryBuilder;
71 } 83 }
72 84
73 /** 85 /**
  86 + * 个性化时添加时间维度的降分
  87 + *
  88 + * @param functionScoreQueryBuilder
  89 + * @param paramMap
  90 + */
  91 + private void addFirstShelveTimeFunctionScore(FunctionScoreQueryBuilder functionScoreQueryBuilder, Map<String, String> paramMap) {
  92 + if (searchCommonHelper.isNewRecPageDefault(paramMap)) {
  93 + this.addFirstShelveTimeScore(functionScoreQueryBuilder, newRecShelveTimeScore);
  94 + } else {
  95 + this.addFirstShelveTimeScore(functionScoreQueryBuilder, commonFirstShelveTimeScore);
  96 + }
  97 + }
  98 +
  99 + private void addFirstShelveTimeScore(FunctionScoreQueryBuilder functionScoreQueryBuilder, FirstShelveTimeScore firstShelveTimeScore) {
  100 + int todayLastSecond = DateUtil.getLastTimeSecond(new Date());
  101 + int limitSecondValue = todayLastSecond - firstShelveTimeScore.getLimitDayCount() * oneDaySecondCount;
  102 + int scaleSecondTime = firstShelveTimeScore.getScaleDayCount() * oneDaySecondCount;
  103 + int offsetSecondValue = firstShelveTimeScore.getOffsetDayCount() * oneDaySecondCount;
  104 + functionScoreQueryBuilder.add(QueryBuilders.rangeQuery("firstShelveTime").lt(limitSecondValue), ScoreFunctionBuilders.weightFactorFunction(0.5f));
  105 + functionScoreQueryBuilder.add(QueryBuilders.rangeQuery("firstShelveTime").from(limitSecondValue),
  106 + ScoreFunctionBuilders.linearDecayFunction("firstShelveTime", todayLastSecond, scaleSecondTime).setOffset(offsetSecondValue));
  107 + }
  108 +
  109 + /**
74 * 直接使用商品特征的向量用来做个性化打分 110 * 直接使用商品特征的向量用来做个性化打分
75 * 111 *
76 * @param queryBuilder 112 * @param queryBuilder
@@ -91,7 +127,7 @@ public class FunctionScoreSearchHelper { @@ -91,7 +127,7 @@ public class FunctionScoreSearchHelper {
91 * @param descoreGender 127 * @param descoreGender
92 * @return 128 * @return
93 */ 129 */
94 - public float getDescoreGenderWeight(String uid, float baseScore, String descoreGender) { 130 + private float getDescoreGenderWeight(String uid, float baseScore, String descoreGender) {
95 try { 131 try {
96 Map<String, Float> userGenderFloat = personalizedRedisService.getUserGenderFeature(uid); 132 Map<String, Float> userGenderFloat = personalizedRedisService.getUserGenderFeature(uid);
97 Float userGenderWeight = userGenderFloat.get(descoreGender); 133 Float userGenderWeight = userGenderFloat.get(descoreGender);
@@ -116,7 +152,7 @@ public class FunctionScoreSearchHelper { @@ -116,7 +152,7 @@ public class FunctionScoreSearchHelper {
116 152
117 // 潮童频道,对非潮童频道的和成人的商品降分 153 // 潮童频道,对非潮童频道的和成人的商品降分
118 if (physicalChannel.equals("3")) { 154 if (physicalChannel.equals("3")) {
119 - results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder,physicalChannelWeight)); 155 + results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder, physicalChannelWeight));
120 results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("ageLevel", "1"), physicalChannelWeight)); 156 results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("ageLevel", "1"), physicalChannelWeight));
121 return results; 157 return results;
122 } 158 }
@@ -132,14 +168,14 @@ public class FunctionScoreSearchHelper { @@ -132,14 +168,14 @@ public class FunctionScoreSearchHelper {
132 } 168 }
133 // 男生频道,对非男生频道的商品和性别女的降分 169 // 男生频道,对非男生频道的商品和性别女的降分
134 if (physicalChannel.equals("1")) { 170 if (physicalChannel.equals("1")) {
135 - results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder,physicalChannelWeight)); 171 + results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder, physicalChannelWeight));
136 results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("gender", "2"), this.getDescoreGenderWeight(uid, physicalChannelWeight, "2"))); 172 results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("gender", "2"), this.getDescoreGenderWeight(uid, physicalChannelWeight, "2")));
137 return results; 173 return results;
138 } 174 }
139 // 女生频道,对非女生频道的商品和性别男的降分 175 // 女生频道,对非女生频道的商品和性别男的降分
140 if (physicalChannel.equals("2")) { 176 if (physicalChannel.equals("2")) {
141 - results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder,physicalChannelWeight));  
142 - results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("gender", "1"),this.getDescoreGenderWeight(uid, physicalChannelWeight, "1"))); 177 + results.add(new PhysicalChannelScore(notPhysicalChannelsQueryBuilder, physicalChannelWeight));
  178 + results.add(new PhysicalChannelScore(QueryBuilders.termsQuery("gender", "1"), this.getDescoreGenderWeight(uid, physicalChannelWeight, "1")));
143 return results; 179 return results;
144 } 180 }
145 return new ArrayList<PhysicalChannelScore>(); 181 return new ArrayList<PhysicalChannelScore>();
@@ -117,13 +117,13 @@ public class SearchCommonHelper { @@ -117,13 +117,13 @@ public class SearchCommonHelper {
117 */ 117 */
118 public boolean isNeedDeScoreForChannel(Map<String, String> paramMap) { 118 public boolean isNeedDeScoreForChannel(Map<String, String> paramMap) {
119 String physicalChannel = paramMap.get(SearchRequestParams.PHYSICAL_CHANNEL); 119 String physicalChannel = paramMap.get(SearchRequestParams.PHYSICAL_CHANNEL);
120 - if(StringUtils.isBlank(physicalChannel)){ 120 + if (StringUtils.isBlank(physicalChannel)) {
121 return false; 121 return false;
122 } 122 }
123 - if(!isFuzzySearchDefault(paramMap)){ 123 + if (!isFuzzySearchDefault(paramMap)) {
124 return false; 124 return false;
125 } 125 }
126 - if(!dynamicConfig.isDeScorePhysicalChannelOpen()){ 126 + if (!dynamicConfig.isDeScorePhysicalChannelOpen()) {
127 return false; 127 return false;
128 } 128 }
129 return true; 129 return true;
@@ -187,6 +187,25 @@ public class SearchCommonHelper { @@ -187,6 +187,25 @@ public class SearchCommonHelper {
187 } 187 }
188 188
189 /** 189 /**
  190 + * 是否是新品到着默认页
  191 + *
  192 + * @param paramMap
  193 + * @return
  194 + */
  195 + public boolean isNewRecPageDefault(Map<String, String> paramMap) {
  196 + String pageId = paramMap.get("pageId");
  197 + if (StringUtils.isBlank(pageId)|| !pageId.equals("4")){
  198 + return false;
  199 + }
  200 + String order = paramMap.get("order");
  201 + if (!StringUtils.isBlank(order)) {
  202 + return false;
  203 + }
  204 + return true;
  205 + }
  206 +
  207 +
  208 + /**
190 * 关键字中含性别,则加上性别的过滤条件 209 * 关键字中含性别,则加上性别的过滤条件
191 * 210 *
192 * @param keyword 211 * @param keyword
@@ -156,23 +156,6 @@ public class SearchServiceHelper { @@ -156,23 +156,6 @@ public class SearchServiceHelper {
156 QueryBuilder queryBuilder = this.constructQueryBuilder(paramMap); 156 QueryBuilder queryBuilder = this.constructQueryBuilder(paramMap);
157 queryBuilder = functionScoreSearchHelper.buildFunctionScoreQueryBuild(queryBuilder, paramMap); 157 queryBuilder = functionScoreSearchHelper.buildFunctionScoreQueryBuild(queryBuilder, paramMap);
158 return queryBuilder; 158 return queryBuilder;
159 - // queryBuilder = this.buildPersonalSearch(queryBuilder, paramMap);  
160 - // queryBuilder = this.buildFunctionScoreQueryBuild(queryBuilder,  
161 - // paramMap);  
162 - // return queryBuilder;  
163 -  
164 - // String dynamicRuleValue =  
165 - // dynamicSearchRuleHelper.getDynamicRuleValue(paramMap);  
166 - // if (StringUtils.isEmpty(dynamicRuleValue) ||  
167 - // "-1".equals(dynamicRuleValue)) {  
168 - // queryBuilder = this.buildGlobalSearch(queryBuilder, paramMap);  
169 - // queryBuilder = this.buildDeScoreBrandSearch(queryBuilder, paramMap);  
170 - // } else {  
171 - // queryBuilder =  
172 - // dynamicSearchRuleHelper.buildDynamicSerach(queryBuilder, paramMap,  
173 - // dynamicRuleValue);  
174 - // }  
175 - // return queryBuilder;  
176 } 159 }
177 160
178 public QueryBuilder constructOrQueryBuilderForProductList(Map<String, String> paramMap) { 161 public QueryBuilder constructOrQueryBuilderForProductList(Map<String, String> paramMap) {
@@ -649,13 +632,17 @@ public class SearchServiceHelper { @@ -649,13 +632,17 @@ public class SearchServiceHelper {
649 } 632 }
650 } 633 }
651 634
  635 + public List<Map<String, Object>> getProductMapList(List<Map<String, Object>> resultList) {
  636 + return this.getProductMapList(resultList, null);
  637 + }
  638 +
652 /** 639 /**
653 * 获取商品列表返回结果 640 * 获取商品列表返回结果
654 * 641 *
655 * @param resultList 642 * @param resultList
656 * @return 643 * @return
657 */ 644 */
658 - public List<Map<String, Object>> getProductMapList(List<Map<String, Object>> resultList) { 645 + public List<Map<String, Object>> getProductMapList(List<Map<String, Object>> resultList, List<String> extendedFields) {
659 // 获取搜索结果的skn,根据它们来获取product_price_plan 646 // 获取搜索结果的skn,根据它们来获取product_price_plan
660 String[] sknStr = new String[resultList.size()]; 647 String[] sknStr = new String[resultList.size()];
661 for (int i = 0; i < resultList.size(); i++) { 648 for (int i = 0; i < resultList.size(); i++) {
@@ -664,7 +651,7 @@ public class SearchServiceHelper { @@ -664,7 +651,7 @@ public class SearchServiceHelper {
664 List<Map<String, Object>> pageList = new ArrayList<Map<String, Object>>(); 651 List<Map<String, Object>> pageList = new ArrayList<Map<String, Object>>();
665 Map<String, List<Map<String, Object>>> productPricePlanMap = this.searchProductPricePlan(sknStr); 652 Map<String, List<Map<String, Object>>> productPricePlanMap = this.searchProductPricePlan(sknStr);
666 for (Map<String, Object> map : resultList) { 653 for (Map<String, Object> map : resultList) {
667 - Map<String, Object> productMap = getProductMap(map); 654 + Map<String, Object> productMap = getProductMap(map, extendedFields);
668 productMap.put("product_price_plan_list", productPricePlanMap.get("" + map.get("productSkn"))); 655 productMap.put("product_price_plan_list", productPricePlanMap.get("" + map.get("productSkn")));
669 pageList.add(productMap); 656 pageList.add(productMap);
670 } 657 }
@@ -677,8 +664,12 @@ public class SearchServiceHelper { @@ -677,8 +664,12 @@ public class SearchServiceHelper {
677 return result; 664 return result;
678 } 665 }
679 666
680 - @SuppressWarnings("unchecked")  
681 public Map<String, Object> getProductMap(Map<String, Object> map) { 667 public Map<String, Object> getProductMap(Map<String, Object> map) {
  668 + return this.getProductMap(map, null);
  669 + }
  670 +
  671 + @SuppressWarnings("unchecked")
  672 + public Map<String, Object> getProductMap(Map<String, Object> map, List<String> extendedFields) {
682 Map<String, Object> productMap = new HashMap<String, Object>(); 673 Map<String, Object> productMap = new HashMap<String, Object>();
683 productMap.put("cn_alphabet", map.get("cnAlphabet") == null ? "" : map.get("cnAlphabet")); 674 productMap.put("cn_alphabet", map.get("cnAlphabet") == null ? "" : map.get("cnAlphabet"));
684 if (map.containsKey("_highlight") && map.get("_highlight") != null) { 675 if (map.containsKey("_highlight") && map.get("_highlight") != null) {
@@ -727,7 +718,6 @@ public class SearchServiceHelper { @@ -727,7 +718,6 @@ public class SearchServiceHelper {
727 productMap.put("sales_num", map.get("salesNum")); 718 productMap.put("sales_num", map.get("salesNum"));
728 productMap.put("status", map.get("status")); 719 productMap.put("status", map.get("status"));
729 productMap.put("is_promotion", map.get("ispromotion")); 720 productMap.put("is_promotion", map.get("ispromotion"));
730 - productMap.put("is_promotion", map.get("ispromotion"));  
731 String yohoodIdFromMap = (String) map.get("yohoodId"); 721 String yohoodIdFromMap = (String) map.get("yohoodId");
732 if (yohoodIdFromMap != null && yohoodIdFromMap.length() > 0) { 722 if (yohoodIdFromMap != null && yohoodIdFromMap.length() > 0) {
733 productMap.put("yohood_id", yohoodIdFromMap); 723 productMap.put("yohood_id", yohoodIdFromMap);
@@ -761,6 +751,12 @@ public class SearchServiceHelper { @@ -761,6 +751,12 @@ public class SearchServiceHelper {
761 String tbl_plane = (tbl_country_id != null && tbl_country_id != 86) ? "Y" : "N"; 751 String tbl_plane = (tbl_country_id != null && tbl_country_id != 86) ? "Y" : "N";
762 productMap.put("tbl_plane", tbl_plane); 752 productMap.put("tbl_plane", tbl_plane);
763 productMap.put("skn_default_img", map.get("sknDefaultImg")); 753 productMap.put("skn_default_img", map.get("sknDefaultImg"));
  754 + // 一些特殊场景需要额外返回一些参数
  755 + if (extendedFields != null) {
  756 + for (String extendedField : extendedFields) {
  757 + productMap.put(extendedField, map.get(extendedField));
  758 + }
  759 + }
764 return productMap; 760 return productMap;
765 } 761 }
766 } 762 }
  1 +package com.yoho.search.service.servicenew;
  2 +
  3 +import java.util.Map;
  4 +
  5 +import com.yoho.search.service.vo.SearchApiResult;
  6 +
  7 +public interface IGoodProductsService {
  8 +
  9 + /**
  10 + * 有好货商品列表接口
  11 + * @param paramMap
  12 + * @return
  13 + */
  14 + public SearchApiResult goodProductList(Map<String, String> paramMap);
  15 +
  16 +}
  1 +package com.yoho.search.service.servicenew.impl;
  2 +
  3 +import java.util.ArrayList;
  4 +import java.util.Arrays;
  5 +import java.util.Collections;
  6 +import java.util.HashMap;
  7 +import java.util.Iterator;
  8 +import java.util.List;
  9 +import java.util.Map;
  10 +
  11 +import org.apache.commons.lang.StringUtils;
  12 +import org.elasticsearch.index.query.BoolQueryBuilder;
  13 +import org.elasticsearch.index.query.QueryBuilder;
  14 +import org.elasticsearch.index.query.QueryBuilders;
  15 +import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
  16 +import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
  17 +import org.elasticsearch.search.SearchHit;
  18 +import org.elasticsearch.search.SearchHits;
  19 +import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
  20 +import org.elasticsearch.search.aggregations.Aggregation;
  21 +import org.elasticsearch.search.aggregations.AggregationBuilders;
  22 +import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
  23 +import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket;
  24 +import org.elasticsearch.search.aggregations.bucket.terms.TermsBuilder;
  25 +import org.elasticsearch.search.aggregations.metrics.tophits.TopHits;
  26 +import org.elasticsearch.search.sort.SortBuilder;
  27 +import org.elasticsearch.search.sort.SortBuilders;
  28 +import org.elasticsearch.search.sort.SortOrder;
  29 +import org.springframework.beans.factory.annotation.Autowired;
  30 +import org.springframework.stereotype.Service;
  31 +
  32 +import com.alibaba.fastjson.JSONArray;
  33 +import com.alibaba.fastjson.JSONObject;
  34 +import com.yoho.search.base.utils.ISearchConstants;
  35 +import com.yoho.search.core.es.model.SearchParam;
  36 +import com.yoho.search.core.es.model.SearchResult;
  37 +import com.yoho.search.service.cache.CacheEnum;
  38 +import com.yoho.search.service.personalized.PersonalVectorFeatureSearch;
  39 +import com.yoho.search.service.service.SearchCacheService;
  40 +import com.yoho.search.service.service.SearchCommonService;
  41 +import com.yoho.search.service.service.helper.AggProductListHelper;
  42 +import com.yoho.search.service.service.helper.FunctionScoreSearchHelper;
  43 +import com.yoho.search.service.service.helper.SearchCommonHelper;
  44 +import com.yoho.search.service.service.helper.SearchServiceHelper;
  45 +import com.yoho.search.service.service.helper.SearchSortHelper;
  46 +import com.yoho.search.service.servicenew.IGoodProductsService;
  47 +import com.yoho.search.service.utils.SearchRequestParams;
  48 +import com.yoho.search.service.vo.SearchApiResult;
  49 +
  50 +@Service
  51 +public class GoodProductListService implements IGoodProductsService {
  52 +
  53 + @Autowired
  54 + private SearchCommonService searchCommonService;
  55 + @Autowired
  56 + private SearchServiceHelper searchServiceHelper;
  57 + @Autowired
  58 + private FunctionScoreSearchHelper functionScoreSearchHelper;
  59 + @Autowired
  60 + private SearchCacheService searchCacheService;
  61 + @Autowired
  62 + private AggProductListHelper aggProductListHelper;
  63 + @Autowired
  64 + private SearchSortHelper searchSortHelper;
  65 + @Autowired
  66 + private SearchCommonHelper searchCommonHelper;
  67 + @Autowired
  68 + private PersonalVectorFeatureSearch personalVectorFeatureSearch;
  69 +
  70 + private final int maxSmallSortCount = 20;
  71 + private final int maxProductSknCountPerSort = 5;
  72 + private final int maxCountPerGroup = 10;
  73 + private final float firstSknScore = 300;
  74 + private final float recommendedSknMaxScore = 200;
  75 +
  76 + @Override
  77 + public SearchApiResult goodProductList(Map<String, String> paramMap) {
  78 + // 1、检测分页参数
  79 + int pageSize = StringUtils.isBlank(paramMap.get("viewNum")) ? 30 : Integer.parseInt(paramMap.get("viewNum"));
  80 + int page = StringUtils.isBlank(paramMap.get("page")) ? 1 : Integer.parseInt(paramMap.get("page"));
  81 + if (page < 1 || pageSize < 0) {
  82 + return new SearchApiResult().setCode(400).setMessage("分页参数不合法");
  83 + }
  84 + // 2、先获取用户浏览的SKN对应的品类列表
  85 + List<Integer> smallSortIds = this.getProductSknSmallSortIds(paramMap, maxSmallSortCount);
  86 + // 3、再每个品类下获取5个SKN
  87 + List<String> recommondSkns = this.getRecommondedSkns(smallSortIds, maxProductSknCountPerSort, paramMap);
  88 +
  89 + // 4、构造搜索参数
  90 + SearchParam searchParam = new SearchParam();
  91 + searchParam.setFiter(this.getDefaultBoolQueryBuilder());
  92 + searchParam.setQuery(this.builderGoodProductQueryBuilder(paramMap, recommondSkns));
  93 + searchParam.setAggregationBuilders(null);
  94 + searchParam.setPage(page);
  95 + searchParam.setOffset((page - 1) * pageSize);
  96 + searchParam.setSize(pageSize);
  97 + List<SortBuilder> sortBuilders = new ArrayList<SortBuilder>();
  98 + sortBuilders.add(SortBuilders.fieldSort("_score").order(SortOrder.DESC));
  99 + sortBuilders.add(SortBuilders.fieldSort("salesNum").order(SortOrder.DESC));
  100 + sortBuilders.add(SortBuilders.fieldSort("firstShelveTime").order(SortOrder.DESC));
  101 + sortBuilders.add(SortBuilders.fieldSort("id").order(SortOrder.DESC));
  102 + searchParam.setSortBuilders(sortBuilders);
  103 +
  104 + // 5)从缓存中获取数据
  105 + final String indexName = ISearchConstants.INDEX_NAME_PRODUCT_INDEX;
  106 + CacheEnum cacheEnum = CacheEnum.EHCACHE;
  107 + JSONObject cacheObject = searchCacheService.getJSONObjectFromCache(cacheEnum, indexName, searchParam);
  108 + if (cacheObject != null) {
  109 + return new SearchApiResult().setData(cacheObject);
  110 + }
  111 + // 6)查询ES
  112 + SearchResult searchResult = searchCommonService.doSearch(indexName, searchParam);
  113 + if (searchResult == null) {
  114 + return new SearchApiResult().setCode(500).setMessage("execption");
  115 + }
  116 + // 7)构造返回结果
  117 + JSONObject dataMap = new JSONObject();
  118 + dataMap.put("total", searchResult.getTotal());
  119 + dataMap.put("page", searchResult.getPage());
  120 + dataMap.put("page_size", searchParam.getSize());
  121 + dataMap.put("page_total", searchResult.getTotalPage());
  122 + dataMap.put("product_list", searchServiceHelper.getProductMapList(searchResult.getResultList(), Arrays.asList("phrase")));
  123 +
  124 + // 8)将结果存进缓存
  125 + searchCacheService.addJSONObjectToCache(cacheEnum, indexName, searchParam, dataMap);
  126 + return new SearchApiResult().setData(dataMap);
  127 + }
  128 +
  129 + private QueryBuilder builderGoodProductQueryBuilder(Map<String, String> paramMap, List<String> recommendedSknList) {
  130 + QueryBuilder queryBuilder = QueryBuilders.matchAllQuery();
  131 + FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(queryBuilder);
  132 + // 针对参数里第一个SKN加分
  133 + String productSkns = paramMap.get(SearchRequestParams.PARAM_SYNC_SKN);
  134 + if (!StringUtils.isBlank(productSkns)) {
  135 + functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn", productSkns.split(",")[0]), ScoreFunctionBuilders.weightFactorFunction(firstSknScore));
  136 + }
  137 + // 针对推荐出来的SKN做加分
  138 + if (recommendedSknList != null && !recommendedSknList.isEmpty()) {
  139 + Map<Integer, List<String>> recommondSknMap = this.splitProductSkns(recommendedSknList, maxCountPerGroup);
  140 + float currentGroupScore = recommendedSknMaxScore;
  141 + for (Map.Entry<Integer, List<String>> entry: recommondSknMap.entrySet()) {
  142 + functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn", entry.getValue()), ScoreFunctionBuilders.weightFactorFunction(currentGroupScore));
  143 + currentGroupScore = currentGroupScore - 10;
  144 + }
  145 + }
  146 + // 加上个性化打分
  147 + if (searchCommonHelper.isNeedPersonalSearch(paramMap)) {
  148 + personalVectorFeatureSearch.addPersonalizedScriptScore(functionScoreQueryBuilder, paramMap);
  149 + }
  150 + return functionScoreQueryBuilder;
  151 + }
  152 +
  153 + /**
  154 + * 获取SKN相关的小分类
  155 + *
  156 + * @param productSkns
  157 + * @return
  158 + */
  159 + private List<Integer> getProductSknSmallSortIds(Map<String, String> paramMap, int maxCount) {
  160 + String productSkns = paramMap.get(SearchRequestParams.PARAM_SYNC_SKN);
  161 + if (StringUtils.isBlank(productSkns)) {
  162 + return new ArrayList<Integer>();
  163 + }
  164 + String[] productSknArray = productSkns.split(",");
  165 + SearchParam searchParam = new SearchParam();
  166 + // 1、设置过滤条件
  167 + BoolQueryBuilder boolFilter = QueryBuilders.boolQuery();
  168 + boolFilter.must(QueryBuilders.termsQuery("productSkn", productSknArray));
  169 + searchParam.setFiter(boolFilter);
  170 + // 2、设置聚合条件
  171 + final String aggName = "smallSortAgg";
  172 + TermsBuilder smallSortIdAgg = AggregationBuilders.terms(aggName).field("smallSortId").size(maxCount);
  173 + searchParam.setAggregationBuilders(Arrays.asList(smallSortIdAgg));
  174 + // 3、设置分页
  175 + searchParam.setPage(0);
  176 + searchParam.setSize(0);
  177 + searchParam.setOffset(0);
  178 +
  179 + // 4、先从缓存中获取,如果能取到,则直接返回
  180 + JSONArray sknSmallSortIdJSONArray = searchCacheService.getJSONArrayFromCache(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
  181 + if (sknSmallSortIdJSONArray != null) {
  182 + return this.jsonArrayToList(sknSmallSortIdJSONArray, Integer.class);
  183 + }
  184 + // 5、执行搜索
  185 + SearchResult searchResult = searchCommonService.doSearch(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
  186 + if (searchResult == null || searchResult.getAggMaps() == null || searchResult.getAggMaps().get("smallSortAgg") == null) {
  187 + return new ArrayList<Integer>();
  188 + }
  189 + sknSmallSortIdJSONArray = new JSONArray();
  190 + MultiBucketsAggregation aggregation = (MultiBucketsAggregation) searchResult.getAggMaps().get(aggName);
  191 + Iterator<? extends Bucket> smallSortIdIterator = aggregation.getBuckets().iterator();
  192 + while (smallSortIdIterator.hasNext()) {
  193 + Bucket smallSortIdBucket = smallSortIdIterator.next();
  194 + if (StringUtils.isNumeric(smallSortIdBucket.getKeyAsString())) {
  195 + sknSmallSortIdJSONArray.add(Integer.valueOf(smallSortIdBucket.getKeyAsString()));
  196 + }
  197 + }
  198 + searchCacheService.addJSONArrayToCache(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam, sknSmallSortIdJSONArray);
  199 + return this.jsonArrayToList(sknSmallSortIdJSONArray, Integer.class);
  200 + }
  201 +
  202 + /**
  203 + * 有好货默认的过滤规则
  204 + *
  205 + * @return
  206 + */
  207 + private BoolQueryBuilder getDefaultBoolQueryBuilder() {
  208 + BoolQueryBuilder boolFilter = QueryBuilders.boolQuery();
  209 + boolFilter.mustNot(QueryBuilders.termsQuery("isSeckill", "Y"));
  210 + boolFilter.mustNot(QueryBuilders.termsQuery("isGlobal", "Y"));
  211 + boolFilter.must(QueryBuilders.termQuery("status", 1));
  212 + boolFilter.must(QueryBuilders.termQuery("isOutlets", 2));
  213 + boolFilter.must(QueryBuilders.termQuery("attribute", 1));
  214 + // 默认推库存>2,非断码,并且短评存在的数据
  215 + boolFilter.must(QueryBuilders.rangeQuery("storageNum").gte(2));
  216 + boolFilter.must(QueryBuilders.rangeQuery("breakingRate").lt(50));
  217 + boolFilter.must(QueryBuilders.rangeQuery("basePinRatio").lt(3.5));
  218 + boolFilter.must(QueryBuilders.termQuery("isPhraseExist", "Y"));
  219 + return boolFilter;
  220 + }
  221 +
  222 + /**
  223 + * 每个品类为用户推荐maxSize个SKN
  224 + *
  225 + * @param smallSortIds
  226 + * @param maxSize
  227 + * @param paramMap
  228 + * @return
  229 + */
  230 + private List<String> getRecommondedSkns(List<Integer> smallSortIds, int maxSizePerSort, Map<String, String> paramMap) {
  231 + // 1、如果品类为空,则直接返回
  232 + if (smallSortIds == null || smallSortIds.isEmpty()) {
  233 + return new ArrayList<String>();
  234 + }
  235 + SearchParam searchParam = new SearchParam();
  236 +
  237 + // 2、构造filter
  238 + BoolQueryBuilder boolFilter = this.getDefaultBoolQueryBuilder();
  239 + boolFilter.must(QueryBuilders.termsQuery("smallSortId", smallSortIds));
  240 + searchParam.setFiter(boolFilter);
  241 +
  242 + // 3、构造query
  243 + FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(QueryBuilders.matchAllQuery());
  244 + // 针对看过的SKN做加分
  245 + String productSkns = paramMap.get(SearchRequestParams.PARAM_SYNC_SKN);
  246 + if (!StringUtils.isBlank(productSkns)) {
  247 + functionScoreQueryBuilder.add(QueryBuilders.termsQuery("productSkn", productSkns.split(",")), ScoreFunctionBuilders.weightFactorFunction(100));
  248 + }
  249 + // 加上个性化打分
  250 + if (searchCommonHelper.isNeedPersonalSearch(paramMap)) {
  251 + personalVectorFeatureSearch.addPersonalizedScriptScore(functionScoreQueryBuilder, paramMap);
  252 + }
  253 + searchParam.setQuery(functionScoreQueryBuilder);
  254 +
  255 + // 4、设置聚合条件
  256 + final String firstAggName = "firstAgg";
  257 + String order = "_score:desc";
  258 + String sortField = order.split(":")[0];
  259 + SortOrder sortOrder = order.split(":")[1].equals("desc") ? SortOrder.DESC : SortOrder.ASC;
  260 + List<AbstractAggregationBuilder> list = new ArrayList<AbstractAggregationBuilder>();
  261 + // 4.1)构造父聚合:品牌或品类聚合【同时按子聚合的sort字段排序】
  262 + TermsBuilder parentAggregationBuilder = AggregationBuilders.terms(firstAggName).field("smallSortId").size(smallSortIds.size());
  263 + // 4.2)添加子聚合:取得分最大的值
  264 + parentAggregationBuilder.subAggregation(AggregationBuilders.max("sort").field(sortField));
  265 + // 4.3)添加孙聚合:取打分最高的一个product
  266 + parentAggregationBuilder.subAggregation(AggregationBuilders.topHits("product").addSort(SortBuilders.fieldSort(sortField).order(sortOrder)).setSize(maxSizePerSort));
  267 + list.add(parentAggregationBuilder);
  268 + searchParam.setAggregationBuilders(list);
  269 +
  270 + // 5、设置分页
  271 + searchParam.setPage(0);
  272 + searchParam.setSize(0);
  273 + searchParam.setOffset(0);
  274 +
  275 + // 6、先从缓存中获取,如果能取到,则直接返回
  276 + JSONArray recommendedSknJSONArray = searchCacheService.getJSONArrayFromCache(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
  277 + if (recommendedSknJSONArray != null) {
  278 + return this.jsonArrayToList(recommendedSknJSONArray, String.class);
  279 + }
  280 + // 7、执行搜索,并构造返回结果
  281 + final String indexName = ISearchConstants.INDEX_NAME_PRODUCT_INDEX;
  282 + SearchResult searchResult = searchCommonService.doSearch(indexName, searchParam);
  283 + if (searchResult == null || searchResult.getAggMaps() == null) {
  284 + return new ArrayList<String>();
  285 + }
  286 + Map<String, Aggregation> aggMaps = searchResult.getAggMaps();
  287 + if (!aggMaps.containsKey(firstAggName)) {
  288 + return new ArrayList<String>();
  289 + }
  290 + List<String> recommendedSknList = this.getRecommendedSknList((MultiBucketsAggregation) aggMaps.get(firstAggName));
  291 + recommendedSknJSONArray = new JSONArray();
  292 + for (String recommendedSkn : recommendedSknList) {
  293 + recommendedSknJSONArray.add(recommendedSkn);
  294 + }
  295 + searchCacheService.addJSONArrayToCache(indexName, searchParam, recommendedSknJSONArray);
  296 + return this.jsonArrayToList(recommendedSknJSONArray, String.class);
  297 + }
  298 +
  299 + private <T> List<T> jsonArrayToList(JSONArray jsonArray, Class<T> clazz) {
  300 + return JSONObject.parseArray(jsonArray.toJSONString(), clazz);
  301 + }
  302 +
  303 + /**
  304 + * 分组
  305 + *
  306 + * @param recommendedSknList
  307 + * @param maxGroupCount
  308 + * @return
  309 + */
  310 + private Map<Integer, List<String>> splitProductSkns(List<String> recommendedSknList, int maxCountPerGroup) {
  311 + int maxSize = recommendedSknList.size();
  312 + int groupSize = maxSize / maxCountPerGroup;
  313 + if (maxSize % maxCountPerGroup > 0) {
  314 + groupSize = groupSize + 1;
  315 + }
  316 + Map<Integer, List<String>> result = new HashMap<Integer, List<String>>();
  317 + for (int i = 0; i < groupSize; i++) {
  318 + int fromIndex = i * maxCountPerGroup;
  319 + int toIndex = (i + 1) * maxCountPerGroup;
  320 + result.put(i, recommendedSknList.subList(fromIndex, toIndex > maxSize ? maxSize : toIndex));
  321 + }
  322 + return result;
  323 + }
  324 +
  325 + private List<String> getRecommendedSknList(MultiBucketsAggregation aggregation) {
  326 + Iterator<? extends Bucket> itAgg = aggregation.getBuckets().iterator();
  327 + // 获取聚合出来的商品
  328 + List<String> recommendedSknList = new ArrayList<String>();
  329 + while (itAgg.hasNext()) {
  330 + Bucket lt = itAgg.next();
  331 + if (lt.getAggregations().getAsMap().containsKey("product")) {
  332 + TopHits topHits = lt.getAggregations().get("product");
  333 + if (topHits != null) {
  334 + SearchHits hits = topHits.getHits();
  335 + for (SearchHit hit : hits.getHits()) {
  336 + recommendedSknList.add("" + hit.getSource().get("productSkn"));
  337 + }
  338 + }
  339 + }
  340 + }
  341 + Collections.shuffle(recommendedSknList);
  342 + return recommendedSknList;
  343 + }
  344 +
  345 + public static void main(String[] args) {
  346 + List<String> list = new ArrayList<String>();
  347 + for (int i = 1; i <= 99; i++) {
  348 + list.add(i+"");
  349 + }
  350 + Map<Integer, List<String>> results = new GoodProductListService().splitProductSkns(list, 20);
  351 + for (Map.Entry<Integer, List<String>> entry : results.entrySet()) {
  352 + System.out.println(entry.getKey() + "_" + entry.getValue());
  353 + }
  354 + }
  355 +
  356 +}
@@ -197,7 +197,7 @@ public class PromotionServiceImpl implements IPromotionService { @@ -197,7 +197,7 @@ public class PromotionServiceImpl implements IPromotionService {
197 public SearchApiResult list(PromotionConditions promotionConditions, Map<String, String> paramMap) throws Exception { 197 public SearchApiResult list(PromotionConditions promotionConditions, Map<String, String> paramMap) throws Exception {
198 SearchParam searchParam = new SearchParam(); 198 SearchParam searchParam = new SearchParam();
199 // 1、设置查询条件 199 // 1、设置查询条件
200 - searchParam.setQuery(searchServiceHelper.constructQueryBuilder(paramMap)); 200 + searchParam.setQuery(searchServiceHelper.constructQueryBuilderForProductList(paramMap));
201 // 2、设置过滤条件 201 // 2、设置过滤条件
202 BoolQueryBuilder boolQueryBuilder = searchServiceHelper.constructFilterBuilder(paramMap, null); 202 BoolQueryBuilder boolQueryBuilder = searchServiceHelper.constructFilterBuilder(paramMap, null);
203 BoolQueryBuilder mustFilterByPromotion = this.getMustFilterByPromotion(promotionConditions); 203 BoolQueryBuilder mustFilterByPromotion = this.getMustFilterByPromotion(promotionConditions);
  1 +package com.yoho.search.service.vo;
  2 +
  3 +public class FirstShelveTimeScore {
  4 +
  5 + private int limitDayCount;
  6 + private int offsetDayCount;
  7 + private int scaleDayCount;
  8 +
  9 + public FirstShelveTimeScore(int limitDayCount, int offsetDayCount, int scaleDayCount) {
  10 + super();
  11 + this.limitDayCount = limitDayCount;
  12 + this.offsetDayCount = offsetDayCount;
  13 + this.scaleDayCount = scaleDayCount;
  14 + }
  15 +
  16 + public int getLimitDayCount() {
  17 + return limitDayCount;
  18 + }
  19 +
  20 + public int getOffsetDayCount() {
  21 + return offsetDayCount;
  22 + }
  23 +
  24 + public int getScaleDayCount() {
  25 + return scaleDayCount;
  26 + }
  27 +
  28 +}