...
|
...
|
@@ -5,36 +5,25 @@ import java.util.Arrays; |
|
|
import java.util.HashMap;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
import java.util.concurrent.CompletableFuture;
|
|
|
import java.util.concurrent.ExecutorService;
|
|
|
import java.util.concurrent.Executors;
|
|
|
|
|
|
import javax.annotation.PostConstruct;
|
|
|
|
|
|
import org.apache.commons.collections.CollectionUtils;
|
|
|
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.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.terms.Terms;
|
|
|
import org.elasticsearch.search.aggregations.bucket.terms.TermsBuilder;
|
|
|
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.base.utils.ProductIndexEsField;
|
|
|
import com.yoho.search.common.cache.SearchCacheFactory;
|
|
|
import com.yoho.search.common.cache.model.SearchCache;
|
|
|
import com.yoho.search.core.es.model.SearchParam;
|
|
|
import com.yoho.search.core.es.model.SearchResult;
|
|
|
import com.yoho.search.models.SearchApiResult;
|
|
|
import com.yoho.search.service.base.SearchCacheService;
|
|
|
import com.yoho.search.service.base.SearchCommonService;
|
...
|
...
|
@@ -69,13 +58,13 @@ public class SearchLikeSceneService { |
|
|
|
|
|
private SearchCache searchLikeSearchCache;
|
|
|
|
|
|
private ExecutorService executorService = Executors.newFixedThreadPool(100);
|
|
|
|
|
|
@PostConstruct
|
|
|
void init() {
|
|
|
searchLikeSearchCache = searchCacheFactory.getSearchLikeSearchCache();
|
|
|
}
|
|
|
|
|
|
private static final int sameBrandCount = 10;// 获取相同品牌的商品数量
|
|
|
|
|
|
/**
|
|
|
* @找相似功能
|
|
|
*/
|
...
|
...
|
@@ -98,40 +87,36 @@ public class SearchLikeSceneService { |
|
|
String isGlobalInEs = productInfoInEs.getString("isGlobal");
|
|
|
boolean isGlobal = "Y".equalsIgnoreCase(isGlobalInEs);
|
|
|
|
|
|
// 4、获取同一个品牌下最多5个商品
|
|
|
int maxCountInBrand = 5;
|
|
|
|
|
|
// 5.并行去取数据
|
|
|
CompletableFuture<JSONArray> inBrandProductListFuture = CompletableFuture.supplyAsync(
|
|
|
() -> this.getProductListInBrand(productInfoInEs, paramMap, pageSize > maxCountInBrand ? maxCountInBrand : pageSize, isGlobal), executorService);
|
|
|
CompletableFuture<JSONArray> notInBrandProductListFuture = CompletableFuture.supplyAsync(() -> this.getProductListNotInBrand(productInfoInEs, pageSize, isGlobal),
|
|
|
executorService);
|
|
|
JSONArray inBrandProductList = inBrandProductListFuture.get();
|
|
|
JSONArray notInBrandProductList = notInBrandProductListFuture.get();
|
|
|
// 4、设置第一步SearchParam
|
|
|
List<SearchParam> searchParams = new ArrayList<SearchParam>();
|
|
|
searchParams.add(this.buildSearchParam(productInfoInEs, sameBrandCount, true, isGlobal));
|
|
|
searchParams.add(this.buildSearchParam(productInfoInEs, pageSize, false, isGlobal));
|
|
|
|
|
|
// 6、构造返回结果
|
|
|
Map<String, Object> dataMap = new HashMap<String, Object>();
|
|
|
JSONArray productList = new JSONArray();
|
|
|
productList.addAll(inBrandProductList);
|
|
|
productList.addAll(notInBrandProductList);
|
|
|
// 5、从缓存中获取数据,有则直接返回
|
|
|
String productIndexName = ISearchConstants.INDEX_NAME_PRODUCT_INDEX;
|
|
|
JSONObject cacheObject = searchCacheService.getJSONObjectFromCache(searchLikeSearchCache, productIndexName, searchParams);
|
|
|
if (cacheObject != null) {
|
|
|
return new SearchApiResult().setData(cacheObject);
|
|
|
}
|
|
|
|
|
|
// 7、返回个数一共pageSize个
|
|
|
if (productList.size() > pageSize) {
|
|
|
List<Object> tempList = productList.subList(0, pageSize);
|
|
|
if (CollectionUtils.isNotEmpty(tempList)) {
|
|
|
productList = new JSONArray();
|
|
|
for (Object object : tempList) {
|
|
|
productList.add(object);
|
|
|
}
|
|
|
}
|
|
|
// 6、获取搜索结果[并截取条数]
|
|
|
List<Map<String, Object>> tempProductList = searchLikeHelper.queryProductList(searchParams);
|
|
|
if (tempProductList.size() > pageSize) {
|
|
|
tempProductList = tempProductList.subList(0, pageSize);
|
|
|
}
|
|
|
|
|
|
// 7、构造真实返回结果
|
|
|
List<Map<String, Object>> productListResults = new ArrayList<Map<String, Object>>();
|
|
|
if (!tempProductList.isEmpty()) {
|
|
|
productListResults = productIndexBaseService.getProductListWithPricePlan(tempProductList);
|
|
|
}
|
|
|
Map<String, Object> dataMap = new HashMap<String, Object>();
|
|
|
dataMap.put("page", 1);
|
|
|
dataMap.put("page_total", 1);
|
|
|
dataMap.put("page_size", pageSize);
|
|
|
dataMap.put("total", productList.size());
|
|
|
dataMap.put("total", productListResults.size());
|
|
|
dataMap.put("product_info", searchLikeHelper.genProductInfoResult(productInfoInEs));
|
|
|
dataMap.put("product_list", productList);
|
|
|
dataMap.put("product_list", productListResults);
|
|
|
return new SearchApiResult().setData(dataMap);
|
|
|
} catch (Exception e) {
|
|
|
return new SearchApiResult().setData(null).setMessage("searchLike Exception").setCode(500);
|
...
|
...
|
@@ -139,17 +124,23 @@ public class SearchLikeSceneService { |
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 找出该品牌下的最多X个相似商品
|
|
|
* 构造SearchParam
|
|
|
*
|
|
|
* @param productInfoInEs
|
|
|
* @param limitCount
|
|
|
* @param isGlobal
|
|
|
* @return
|
|
|
*/
|
|
|
private JSONArray getProductListInBrand(JSONObject productInfoInEs, Map<String, String> paramMap, int limitCount, boolean isGlobal) {
|
|
|
private SearchParam buildSearchParam(JSONObject productInfoInEs, int limitCount, boolean inBrand, boolean isGlobal) {
|
|
|
SearchParam searchParam = new SearchParam();
|
|
|
|
|
|
// 1、构建Query
|
|
|
QueryBuilder queryBuilder = this.genQueryBuilder(productInfoInEs, true, isGlobal);
|
|
|
QueryBuilder queryBuilder = this.genQueryBuilder(productInfoInEs, inBrand, isGlobal);
|
|
|
searchParam.setQuery(queryBuilder);
|
|
|
|
|
|
// 2、设置过滤条件
|
|
|
List<String> notProductSkns = Arrays.asList(paramMap.get(SearchRequestParams.PARAM_SEARCH_PRODUCT_SKN));
|
|
|
BoolQueryBuilder booleanQueryBuilder = this.genSearchLikeFilterBuilder(productInfoInEs, notProductSkns, true, isGlobal);
|
|
|
List<String> notProductSkns = Arrays.asList(productInfoInEs.getString(ProductIndexEsField.productSkn));
|
|
|
BoolQueryBuilder booleanQueryBuilder = this.genSearchLikeFilterBuilder(productInfoInEs, notProductSkns, inBrand, isGlobal);
|
|
|
searchParam.setFiter(booleanQueryBuilder);
|
|
|
|
|
|
// 3、设置排序规则[按打分排序]
|
...
|
...
|
@@ -159,111 +150,21 @@ public class SearchLikeSceneService { |
|
|
// 4、设置分页参数
|
|
|
searchParam.setOffset(0);
|
|
|
searchParam.setSize(limitCount);
|
|
|
|
|
|
// 5、从缓存中获取数据,有则直接返回
|
|
|
String productIndexName = ISearchConstants.INDEX_NAME_PRODUCT_INDEX;
|
|
|
JSONArray productJSONArray = searchCacheService.getJSONArrayFromCache(searchLikeSearchCache, productIndexName, searchParam);
|
|
|
if (productJSONArray != null) {
|
|
|
return productJSONArray;
|
|
|
}
|
|
|
// 6、执行搜索
|
|
|
SearchResult searchResult = searchCommonService.doSearch(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
|
|
|
if (searchResult == null) {
|
|
|
return new JSONArray();
|
|
|
}
|
|
|
// 7、构造返回结果并加入缓存
|
|
|
List<Map<String, Object>> productList = productIndexBaseService.getProductListWithPricePlan(searchResult.getResultList());
|
|
|
productJSONArray = searchLikeHelper.listToJsonArray(productList);
|
|
|
searchCacheService.addJSONArrayToCache(searchLikeSearchCache, productIndexName, searchParam, productJSONArray);
|
|
|
return productJSONArray;
|
|
|
// 5、设置不包含的字段
|
|
|
List<String> excludeFields = productIndexBaseService.getProductIndexExcludeFields();
|
|
|
searchParam.setExcludeFields(excludeFields);
|
|
|
return searchParam;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 找出不同品牌下的余下商品数量的相似商品
|
|
|
* 构造filter
|
|
|
*
|
|
|
* @param productInfoInEs
|
|
|
* @param notProductSkns
|
|
|
* @param isInBrand
|
|
|
* @param isGlobal
|
|
|
* @return
|
|
|
*/
|
|
|
private JSONArray getProductListNotInBrand(JSONObject productInfoInEs, int limit, boolean isGlobal) {
|
|
|
if (limit <= 0) {
|
|
|
return new JSONArray();
|
|
|
}
|
|
|
SearchParam searchParam = new SearchParam();
|
|
|
// 1、构建Query
|
|
|
QueryBuilder queryBuilder = this.genQueryBuilder(productInfoInEs, false, isGlobal);
|
|
|
searchParam.setQuery(queryBuilder);
|
|
|
|
|
|
// 2、设置过滤条件
|
|
|
BoolQueryBuilder booleanQueryBuilder = this.genSearchLikeFilterBuilder(productInfoInEs, null, false, isGlobal);
|
|
|
searchParam.setFiter(booleanQueryBuilder);
|
|
|
|
|
|
// 3、设置聚合条件
|
|
|
final String firstAggName = "firstAgg";
|
|
|
String sortField = "_score";
|
|
|
SortOrder sortOrder = SortOrder.DESC;
|
|
|
List<AbstractAggregationBuilder> list = new ArrayList<AbstractAggregationBuilder>();
|
|
|
// 3.1)构造父聚合:品牌或品类聚合【同时按子聚合的sort字段排序】
|
|
|
TermsBuilder brandAggregation = AggregationBuilders.terms(firstAggName).field("brandId").order(Terms.Order.aggregation("sort", false)).size(200 + limit);
|
|
|
// 3.2)添加子聚合:取得分最大的值
|
|
|
brandAggregation.subAggregation(AggregationBuilders.max("sort").field(sortField));
|
|
|
// 3.3)添加孙聚合:取打分最高的一个product
|
|
|
brandAggregation.subAggregation(AggregationBuilders.topHits("product").addSort(SortBuilders.fieldSort(sortField).order(sortOrder)).setSize(2));
|
|
|
list.add(brandAggregation);
|
|
|
searchParam.setAggregationBuilders(list);
|
|
|
|
|
|
// 4、设置分页参数
|
|
|
searchParam.setOffset(0);
|
|
|
searchParam.setSize(0);
|
|
|
|
|
|
// 5、先从缓存中获取,如果能取到,则直接返回
|
|
|
JSONArray productJSONArray = searchCacheService.getJSONArrayFromCache(searchLikeSearchCache, ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
|
|
|
if (productJSONArray != null) {
|
|
|
return productJSONArray;
|
|
|
}
|
|
|
// 6、执行搜索,并构造返回结果
|
|
|
final String indexName = ISearchConstants.INDEX_NAME_PRODUCT_INDEX;
|
|
|
SearchResult searchResult = searchCommonService.doSearch(indexName, searchParam);
|
|
|
if (searchResult == null || searchResult.getAggMaps() == null) {
|
|
|
return new JSONArray();
|
|
|
}
|
|
|
Map<String, Aggregation> aggMaps = searchResult.getAggMaps();
|
|
|
if (!aggMaps.containsKey(firstAggName)) {
|
|
|
return new JSONArray();
|
|
|
}
|
|
|
List<Map<String, Object>> productList = aggProductListHelper.getProductListFromAggregation(((MultiBucketsAggregation) aggMaps.get(firstAggName)), limit, sortField,
|
|
|
sortOrder);
|
|
|
productJSONArray = searchLikeHelper.listToJsonArray(productList);
|
|
|
searchCacheService.addJSONArrayToCache(searchLikeSearchCache, indexName, searchParam, productJSONArray);
|
|
|
return productJSONArray;
|
|
|
}
|
|
|
|
|
|
private QueryBuilder genQueryBuilder(JSONObject productInfoInEs, boolean isInBrand, boolean isGlobal) {
|
|
|
StringBuilder query = new StringBuilder();
|
|
|
// 1、如果是全球购,则直接用商品名称+品类名称去查
|
|
|
if (isGlobal) {
|
|
|
this.append(query, productInfoInEs.getString("productName"));
|
|
|
this.append(query, productInfoInEs.getString("smallSort"));
|
|
|
this.append(query, productInfoInEs.getString("middleSort"));
|
|
|
this.append(query, productInfoInEs.getString("maxSort"));
|
|
|
return searchLikeHelper.genSearchLikeQueryBuilder(query.toString(), "25%", null);
|
|
|
}
|
|
|
// 2、设置有货的查询的值
|
|
|
this.append(query, productInfoInEs.getString("productName"));
|
|
|
this.append(query, productInfoInEs.getString("style"));
|
|
|
this.append(query, productInfoInEs.getString("pattern"));
|
|
|
this.append(query, productInfoInEs.getString("attributeNames"));
|
|
|
this.append(query, productInfoInEs.getString("standardOnlyNames"));
|
|
|
String queryString = query.toString();
|
|
|
|
|
|
String brandName = productInfoInEs.getString("brandName");
|
|
|
if (StringUtils.isNotBlank(brandName)) {
|
|
|
if (isInBrand) {
|
|
|
queryString = queryString + " " + brandName;
|
|
|
} else {
|
|
|
queryString = queryString.replaceAll(brandName, "");
|
|
|
}
|
|
|
}
|
|
|
// 3、生成QueryBuilder
|
|
|
return searchLikeHelper.genSearchLikeQueryBuilder(queryString, isInBrand ? "40%" : "30%", productInfoInEs.getString("productFeatureFactor"));
|
|
|
}
|
|
|
|
|
|
private BoolQueryBuilder genSearchLikeFilterBuilder(JSONObject productInfoInEs, List<String> notProductSkns, boolean isInBrand, boolean isGlobal) {
|
|
|
BoolQueryBuilder boolFilter = searchLikeHelper.genDefaultSearchLikeFilter(notProductSkns, isInBrand ? isGlobal : false);// 前几个是全球购,后面找有货的就好
|
|
|
// 1)设置此SKN相关的过滤条件
|
...
|
...
|
@@ -296,9 +197,28 @@ public class SearchLikeSceneService { |
|
|
return boolFilter;
|
|
|
}
|
|
|
|
|
|
private void append(StringBuilder stringBuilder, String word) {
|
|
|
if (StringUtils.isNotBlank(word)) {
|
|
|
stringBuilder.append(word).append(' ');
|
|
|
/**
|
|
|
* 构造query
|
|
|
*
|
|
|
* @param productInfoInEs
|
|
|
* @param notProductSkns
|
|
|
* @param isInBrand
|
|
|
* @param isGlobal
|
|
|
* @return
|
|
|
*/
|
|
|
private QueryBuilder genQueryBuilder(JSONObject productInfoInEs, boolean isInBrand, boolean isGlobal) {
|
|
|
// 1、如果是全球购,则直接用商品名称+品类名称去查
|
|
|
if (isGlobal) {
|
|
|
String queryString = searchLikeHelper.genGlobalQueryString(productInfoInEs);
|
|
|
return searchLikeHelper.genSearchLikeQueryBuilder(queryString, "25%", null);
|
|
|
}
|
|
|
// 2、设置有货的查询的值
|
|
|
if (isInBrand) {
|
|
|
String queryString = searchLikeHelper.genYohoQueryStringWithBrandName(productInfoInEs);
|
|
|
return searchLikeHelper.genSearchLikeQueryBuilder(queryString, "40%", productInfoInEs.getString("productFeatureFactor"));
|
|
|
} else {
|
|
|
String queryString = searchLikeHelper.genYohoQueryStringWithOutBrandName(productInfoInEs);
|
|
|
return searchLikeHelper.genSearchLikeQueryBuilder(queryString, "30%", productInfoInEs.getString("productFeatureFactor"));
|
|
|
}
|
|
|
}
|
|
|
|
...
|
...
|
|