|
|
package com.yoho.search.service.servicenew.impl;
|
|
|
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.yoho.search.base.utils.CharUtils;
|
|
|
import com.yoho.search.base.utils.ISearchConstants;
|
|
|
import com.yoho.search.base.utils.RedisKeys;
|
|
|
import com.yoho.search.core.es.model.SearchParam;
|
|
|
import com.yoho.search.core.es.model.SearchResult;
|
|
|
import com.yoho.search.service.service.SearchCacheService;
|
|
|
import com.yoho.search.service.service.SearchCommonService;
|
|
|
import com.yoho.search.service.service.SearchKeyWordService;
|
|
|
import com.yoho.search.service.service.helper.SearchCommonHelper;
|
|
|
import com.yoho.search.service.service.helper.SearchServiceHelper;
|
|
|
import com.yoho.search.service.servicenew.ISearchRecommendService;
|
|
|
import com.yoho.search.service.servicenew.ISuggestService;
|
|
|
import com.yoho.search.service.utils.SearchRequestParams;
|
|
|
import com.yoho.search.service.vo.SearchApiResult;
|
|
|
import org.apache.commons.collections.CollectionUtils;
|
|
|
import org.apache.commons.lang.StringUtils;
|
|
|
import org.elasticsearch.common.lucene.search.function.CombineFunction;
|
|
|
import org.elasticsearch.index.query.*;
|
|
|
import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
|
|
|
import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
|
|
|
import org.elasticsearch.search.sort.SortBuilder;
|
|
|
import org.elasticsearch.search.sort.SortBuilders;
|
|
|
import org.elasticsearch.search.sort.SortOrder;
|
|
|
import org.slf4j.Logger;
|
|
|
import org.slf4j.LoggerFactory;
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
import org.springframework.stereotype.Component;
|
|
|
import org.springframework.util.Assert;
|
|
|
|
|
|
import java.util.*;
|
|
|
import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* Created by ginozhang on 2017/3/22.
|
|
|
*/
|
|
|
@Component
|
|
|
public class SearchRecommendServiceImpl implements ISearchRecommendService {
|
|
|
|
|
|
private static final Logger logger = LoggerFactory.getLogger(SearchRecommendServiceImpl.class);
|
|
|
|
|
|
private static final Logger CACHE_MATCH_REQUEST = LoggerFactory.getLogger("CACHE_MATCH_REQUEST");
|
|
|
|
|
|
// 返回智能搜索词的数量
|
|
|
private static final int SMART_SUGGESTION_TERM_COUNT = 3;
|
|
|
|
|
|
// 返回智能搜索词限制关联商品数量限制
|
|
|
private static final int SMART_SUGGESTION_COUNT_LIMIT = 20;
|
|
|
|
|
|
private static final List<String> DEFAULT_SUGGEST_TIPS = Arrays.asList("潮流", "时尚", "休闲");
|
|
|
|
|
|
private static final Map<String, String> CUSTOM_SUGGEST_CONVERSIONS = new HashMap<>();
|
|
|
|
|
|
private static final int VALID_STATUS = 1;
|
|
|
|
|
|
@Autowired
|
|
|
private ISuggestService suggestService;
|
|
|
@Autowired
|
|
|
private SearchCommonService searchCommonService;
|
|
|
@Autowired
|
|
|
private SearchCommonHelper searchCommonHelper;
|
|
|
@Autowired
|
|
|
private SearchCacheService searchCacheService;
|
|
|
@Autowired
|
|
|
private SearchKeyWordService searchKeyWordService;
|
|
|
@Autowired
|
|
|
private SearchServiceHelper searchServiceHelper;
|
|
|
|
|
|
|
|
|
static {
|
|
|
// it是一个停用词 所以直接配置转换关系
|
|
|
CUSTOM_SUGGEST_CONVERSIONS.put("it", ":CHOCOOLATE,izzue,5cm");
|
|
|
}
|
|
|
|
|
|
|
|
|
/**
|
|
|
* 根据query关键词和搜索结果获取搜索推荐词。
|
|
|
* 用于搜索结果数量太少或者无结果的时候给予用户的搜索建议。
|
|
|
*
|
|
|
* @param searchResult 搜索结果
|
|
|
* @param paramMap 商品列表搜索参数
|
|
|
* @return 包括term建议和phrase建议。
|
|
|
*/
|
|
|
@Override
|
|
|
public JSONObject recommend(SearchApiResult searchResult, Map<String, String> paramMap) {
|
|
|
String queryWord = paramMap.get(SearchRequestParams.PARAM_SEARCH_KEYWORD);
|
|
|
long begin = System.currentTimeMillis();
|
|
|
logger.info("[func=recommend][queryWord={}][begin={}]", queryWord, begin);
|
|
|
|
|
|
try {
|
|
|
Assert.isTrue(StringUtils.isNotEmpty(queryWord), "query keyword cannot be empty.");
|
|
|
Assert.isTrue(searchResult != null && searchResult.getData() != null, "SearchResult is invalid.");
|
|
|
String keywordsToSearch = queryWord;
|
|
|
|
|
|
// 搜索推荐分两部分
|
|
|
// 1) 第一部分是最常见的情况,包括有结果、根据SKN搜索、关键词未出现在空结果Redis ZSet里
|
|
|
if (containsProductInSearchResult(searchResult)) {
|
|
|
// 1.1) 搜索有结果的 优先从搜索结果聚合出品牌等关键词进行查询
|
|
|
JSONObject dataMap = ((JSONObject) searchResult.getData());
|
|
|
List<Map<String, Object>> productList = (List<Map<String, Object>>) dataMap.get("product_list");
|
|
|
String aggKeywords = aggKeywordsByProductList(productList);
|
|
|
keywordsToSearch = keywordsToSearch + " " + aggKeywords;
|
|
|
} else if (isQuerySkn(queryWord)) {
|
|
|
// 1.2) 如果是查询SKN 没有查询到的 后续的逻辑也无法推荐 所以直接到ES里去获取关键词
|
|
|
keywordsToSearch = aggKeywordsBySkns(queryWord);
|
|
|
if (StringUtils.isEmpty(keywordsToSearch)) {
|
|
|
return defaultSuggestRecommendation();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
Double count = searchKeyWordService.getKeywordCount(RedisKeys.YOHO_SEARCH_KEYWORDS_EMPTY, queryWord);
|
|
|
if (count == null || queryWord.length() >= 5) {
|
|
|
// 1.3) 如果该关键词一次都没有出现在空结果列表或者长度大于5 则该词很有可能是可以搜索出结果的 因此优先取suggest去搜索一把 减少后面的查询动作
|
|
|
JSONObject recommendResult = recommendBySuggestIndex(paramMap, keywordsToSearch, false);
|
|
|
if (recommendResult == null) {
|
|
|
return defaultSuggestRecommendation();
|
|
|
} else if (CollectionUtils.isNotEmpty((List) recommendResult.get("terms_suggestion"))) {
|
|
|
logger.info("[func=recommend][queryWord={}][cost={}]", queryWord, System.currentTimeMillis() - begin);
|
|
|
return recommendResult;
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 2) 第二部分是通过Conversion和拼写纠错去获取关键词 由于很多品牌的拼写可能比较相近 因此先走Conversion然后再拼写检查
|
|
|
String spellingCorrentWord = null, dest = null;
|
|
|
if (allowGetingDest(queryWord) && StringUtils.isNotEmpty((dest = getSuggestConversionDestBySource(queryWord)))) {
|
|
|
// 2.1) 爬虫和自定义的Conversion处理
|
|
|
keywordsToSearch = dest;
|
|
|
} else if (allowSpellingCorrent(queryWord) && StringUtils.isNotEmpty((spellingCorrentWord = suggestService.getSpellingCorrectKeyword(queryWord)))) {
|
|
|
// 2.2) 执行拼写检查 由于在搜索建议的时候会进行拼写检查 所以缓存命中率高
|
|
|
keywordsToSearch = spellingCorrentWord;
|
|
|
} else {
|
|
|
// 2.3) 如果两者都没有 则直接返回
|
|
|
return defaultSuggestRecommendation();
|
|
|
}
|
|
|
|
|
|
JSONObject recommendResult = recommendBySuggestIndex(paramMap, keywordsToSearch, dest != null);
|
|
|
if (recommendResult == null || CollectionUtils.isEmpty((List) recommendResult.get("terms_suggestion"))) {
|
|
|
recommendResult = defaultSuggestRecommendation();
|
|
|
}
|
|
|
|
|
|
logger.info("[func=recommend][queryWord={}][cost={}]", queryWord, System.currentTimeMillis() - begin);
|
|
|
return recommendResult;
|
|
|
} catch (Exception e) {
|
|
|
logger.error("[func=recommend][queryWord=" + queryWord + "]", e);
|
|
|
return defaultSuggestRecommendation();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
private boolean allowGetingDest(String queryWord) {
|
|
|
// 对于conversion长度要求在[2,10]之间
|
|
|
if (queryWord.length() < 2 || queryWord.length() > 10) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 不能有空格、加号、逗号等符号
|
|
|
if (queryWord.contains(" ") || queryWord.contains("+") || queryWord.contains(",") || queryWord.contains("%")) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
private boolean allowSpellingCorrent(String queryWord) {
|
|
|
// 对于拼写检查长度要求在[4,20]之间
|
|
|
if (queryWord.length() < 4 || queryWord.length() > 20) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
// 不能有空格、加号、逗号等符号
|
|
|
if (queryWord.contains(" ") || queryWord.contains("+") || queryWord.contains(",") || queryWord.contains("%")) {
|
|
|
return false;
|
|
|
}
|
|
|
|
|
|
return true;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 根据计算获取的推荐词列表到suggest索引获取推荐词。
|
|
|
* 多个词的情况下优先会给匹配到更多term的词加分。
|
|
|
*
|
|
|
* @param paramMap 搜索参数,用于判断取哪个count
|
|
|
* @param keywordsToSearch 计算获取的推荐词列表
|
|
|
* @param isLimitKeywords 是否限制只能是推荐词列表里面的词
|
|
|
* @return 搜推荐结果
|
|
|
*/
|
|
|
private JSONObject recommendBySuggestIndex(Map<String, String> paramMap, String keywordsToSearch, boolean isLimitKeywords) {
|
|
|
String srcQueryWord = paramMap.get(SearchRequestParams.PARAM_SEARCH_KEYWORD);
|
|
|
long begin = System.currentTimeMillis();
|
|
|
logger.info("[func=recommendBySuggestIndex][srcQueryWord={}][keywordsToSearch={}][begin={}]", srcQueryWord, keywordsToSearch, begin);
|
|
|
|
|
|
// 1) 如果是查询SKN 直接返回 前面已经处理过了
|
|
|
if (StringUtils.isEmpty(keywordsToSearch)) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
// 2) 先对keywordsToSearch进行分词
|
|
|
List<String> terms = null;
|
|
|
if (isLimitKeywords) {
|
|
|
terms = Arrays.stream(keywordsToSearch.split(",")).filter(term -> term != null && term.length() > 1).distinct().collect(Collectors.toList());
|
|
|
} else {
|
|
|
terms = searchKeyWordService.getAnalyzeTerms(keywordsToSearch, "ik_smart", true);
|
|
|
}
|
|
|
|
|
|
if (CollectionUtils.isEmpty(terms)) {
|
|
|
return new JSONObject();
|
|
|
}
|
|
|
|
|
|
Set<String> termSet = terms.stream().collect(Collectors.toSet());
|
|
|
logger.info("[func=recommendBySuggestIndex][termSet={}]", termSet);
|
|
|
|
|
|
// 3) 根据terms搜索构造搜索请求
|
|
|
final String countField = suggestService.getCountField(paramMap);
|
|
|
SearchParam searchParam = new SearchParam();
|
|
|
|
|
|
QueryBuilder queryBuilder = isLimitKeywords ? buildQueryBuilderByLimit(terms) : buildQueryBuilder(keywordsToSearch, termSet);
|
|
|
searchParam.setQuery(queryBuilder);
|
|
|
searchParam.setPage(1);
|
|
|
searchParam.setSize(SMART_SUGGESTION_TERM_COUNT);
|
|
|
|
|
|
// 3.5) 设置过滤条件
|
|
|
BoolQueryBuilder boolFilter = QueryBuilders.boolQuery();
|
|
|
boolFilter.must(QueryBuilders.termQuery("status", VALID_STATUS));
|
|
|
boolFilter.must(QueryBuilders.rangeQuery(countField).gte(SMART_SUGGESTION_COUNT_LIMIT));
|
|
|
boolFilter.mustNot(QueryBuilders.termQuery("standardKeyword", CharUtils.standardized(srcQueryWord)));
|
|
|
if (isLimitKeywords) {
|
|
|
boolFilter.must(QueryBuilders.termsQuery("standardKeyword", termSet.stream().map(term -> CharUtils.standardized(term)).collect(Collectors.toList())));
|
|
|
}
|
|
|
searchParam.setFiter(boolFilter);
|
|
|
|
|
|
// 3.6) 按照得分、权重、数量的规则降序排序
|
|
|
List<SortBuilder> sortBuilders = new ArrayList<>(3);
|
|
|
sortBuilders.add(SortBuilders.fieldSort("_score").order(SortOrder.DESC));
|
|
|
sortBuilders.add(SortBuilders.fieldSort("weight").order(SortOrder.DESC));
|
|
|
sortBuilders.add(SortBuilders.fieldSort(countField).order(SortOrder.DESC));
|
|
|
searchParam.setSortBuilders(sortBuilders);
|
|
|
|
|
|
// 4) 先从缓存中获取
|
|
|
final String indexName = ISearchConstants.INDEX_NAME_SUGGEST;
|
|
|
JSONObject suggestResult = searchCacheService.getJSONObjectFromCache(indexName, searchParam);
|
|
|
if (suggestResult != null) {
|
|
|
CACHE_MATCH_REQUEST.info("match cache for product list search recommendation by suggest, keyword = {}.", keywordsToSearch);
|
|
|
return suggestResult;
|
|
|
}
|
|
|
|
|
|
// 5) 调用ES执行搜索
|
|
|
SearchResult searchResult = searchCommonService.doSearch(ISearchConstants.INDEX_NAME_SUGGEST, searchParam);
|
|
|
if (searchResult == null) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
// 6) 构建结果加入缓存
|
|
|
suggestResult = new JSONObject();
|
|
|
List<String> resultTerms = searchResult.getResultList().stream().map(map -> (String) map.get("keyword")).collect(Collectors.toList());
|
|
|
suggestResult.put("terms_suggestion", resultTerms);
|
|
|
searchCacheService.addJSONObjectToCache(indexName, searchParam, suggestResult);
|
|
|
logger.info("[func=recommendBySuggestIndex][srcQueryWord={}][keywordsToSearch={}][resultTerms={}][cost={}]",
|
|
|
srcQueryWord, keywordsToSearch, resultTerms, System.currentTimeMillis() - begin);
|
|
|
return suggestResult;
|
|
|
}
|
|
|
|
|
|
private QueryBuilder buildQueryBuilderByLimit(List<String> terms) {
|
|
|
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(QueryBuilders.matchAllQuery());
|
|
|
|
|
|
// 给品类类型的关键词加分
|
|
|
functionScoreQueryBuilder.add(QueryBuilders.termQuery("type", Integer.valueOf(2)),
|
|
|
ScoreFunctionBuilders.weightFactorFunction(3));
|
|
|
|
|
|
// 按词出现的顺序加分
|
|
|
for (int i = 0; i < terms.size(); i++) {
|
|
|
functionScoreQueryBuilder.add(QueryBuilders.termQuery("standardKeyword", CharUtils.standardized(terms.get(i))),
|
|
|
ScoreFunctionBuilders.weightFactorFunction(terms.size() - i));
|
|
|
}
|
|
|
|
|
|
functionScoreQueryBuilder.boostMode(CombineFunction.SUM);
|
|
|
return functionScoreQueryBuilder;
|
|
|
}
|
|
|
|
|
|
private QueryBuilder buildQueryBuilder(String keywordsToSearch, Set<String> termSet) {
|
|
|
// 3.1) 对于suggest的multi-fields至少要有一个字段匹配到 匹配打分为常量1
|
|
|
MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keywordsToSearch.trim().toLowerCase(),
|
|
|
"keyword", "keyword.keyword_ik", "keyword.keyword_pinyin", "keyword.keyword_jianpin", "keyword.keyword_lowercase")
|
|
|
.analyzer("ik_smart")
|
|
|
.type(MultiMatchQueryBuilder.Type.BEST_FIELDS)
|
|
|
.operator(MatchQueryBuilder.Operator.OR)
|
|
|
.minimumShouldMatch("1");
|
|
|
|
|
|
|
|
|
FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(QueryBuilders.constantScoreQuery(queryBuilder));
|
|
|
for (String term : termSet) {
|
|
|
// 3.2) 对于完全匹配Term的加1分
|
|
|
functionScoreQueryBuilder.add(QueryBuilders.termQuery("standardKeyword", CharUtils.standardized(term)),
|
|
|
ScoreFunctionBuilders.weightFactorFunction(1));
|
|
|
|
|
|
// 3.3) 对于匹配到一个Term的加2分
|
|
|
functionScoreQueryBuilder.add(QueryBuilders.termQuery("keyword.keyword_ik", term),
|
|
|
ScoreFunctionBuilders.weightFactorFunction(2));
|
|
|
}
|
|
|
|
|
|
// 3.4) 处理性别相关的关键词
|
|
|
if (termSet.contains("男") && !termSet.contains("女")) {
|
|
|
functionScoreQueryBuilder.add(QueryBuilders.termQuery("keyword.keyword_ik", "女"),
|
|
|
ScoreFunctionBuilders.weightFactorFunction(-5));
|
|
|
} else if (!termSet.contains("男") && termSet.contains("女")) {
|
|
|
functionScoreQueryBuilder.add(QueryBuilders.termQuery("keyword.keyword_ik", "男"),
|
|
|
ScoreFunctionBuilders.weightFactorFunction(-5));
|
|
|
} else if (!termSet.contains("男") && !termSet.contains("女")) {
|
|
|
functionScoreQueryBuilder.add(QueryBuilders.termsQuery("keyword.keyword_ik", Arrays.asList("男", "女")),
|
|
|
ScoreFunctionBuilders.weightFactorFunction(-2));
|
|
|
}
|
|
|
|
|
|
functionScoreQueryBuilder.boostMode(CombineFunction.SUM);
|
|
|
return functionScoreQueryBuilder;
|
|
|
}
|
|
|
|
|
|
private boolean containsProductInSearchResult(SearchApiResult searchResult) {
|
|
|
JSONObject dataMap = ((JSONObject) searchResult.getData());
|
|
|
return CollectionUtils.isNotEmpty((List) dataMap.get("product_list"));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 根据商品列表搜索结果聚合去关键词
|
|
|
* 当推荐词为3个时 品牌和品类最多各两个
|
|
|
*
|
|
|
* @param productList 商品列表搜索结果
|
|
|
* @return 关键词列表
|
|
|
*/
|
|
|
private String aggKeywordsByProductList(List<Map<String, Object>> productList) {
|
|
|
Map<String, Double> brandMap = new HashMap<>(productList.size());
|
|
|
Map<String, Double> sortNameMap = new HashMap<>(productList.size());
|
|
|
for (Map<String, Object> product : productList) {
|
|
|
String brandName = (String) product.get("brand_name");
|
|
|
if (StringUtils.isNotEmpty(brandName)) {
|
|
|
brandMap.compute(brandName, (k, v) -> v == null ? 1.0D : v + 1.0D);
|
|
|
}
|
|
|
|
|
|
String sortName = (String) product.get("small_sort_name");
|
|
|
if (StringUtils.isNotEmpty(sortName)) {
|
|
|
sortNameMap.compute(sortName, (k, v) -> v == null ? 1.0D : v + 1.0D);
|
|
|
}
|
|
|
}
|
|
|
|
|
|
// 分别获取前两个品牌、分类
|
|
|
List<String> sortNameList = com.yoho.search.base.utils.CollectionUtils.getSortedKeys(sortNameMap, false)
|
|
|
.stream().limit(SMART_SUGGESTION_TERM_COUNT - 1).collect(Collectors.toList());
|
|
|
List<String> brandNameList = com.yoho.search.base.utils.CollectionUtils.getSortedKeys(brandMap, false)
|
|
|
.stream().limit(SMART_SUGGESTION_TERM_COUNT - 1).collect(Collectors.toList());
|
|
|
|
|
|
List<String> keywords = new ArrayList<>();
|
|
|
keywords.addAll(sortNameList);
|
|
|
keywords.addAll(brandNameList);
|
|
|
|
|
|
return keywords.isEmpty() ? "" : keywords.stream().collect(Collectors.joining(" "));
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 根据SKN到productindex索引获取搜索结果,然后聚合出推荐词列表。
|
|
|
*
|
|
|
* @param queryWord 搜索的关键词
|
|
|
* @return 推荐词列表
|
|
|
*/
|
|
|
private String aggKeywordsBySkns(String queryWord) {
|
|
|
String sknStr = queryWord.toLowerCase().trim();
|
|
|
if (sknStr.startsWith("skn")) {
|
|
|
sknStr = sknStr.substring("skn".length()).trim();
|
|
|
}
|
|
|
|
|
|
List<Integer> sknList = searchCommonHelper.getQuerySknList(sknStr);
|
|
|
if (CollectionUtils.isEmpty(sknList)) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
int size = sknList.size() > SMART_SUGGESTION_COUNT_LIMIT ? SMART_SUGGESTION_COUNT_LIMIT : sknList.size();
|
|
|
SearchParam productIndexSearchParam = new SearchParam();
|
|
|
productIndexSearchParam.setSize(size);
|
|
|
productIndexSearchParam.setFiter(QueryBuilders.termsQuery("productSkn", sknList));
|
|
|
|
|
|
// 缓存和列表的很难命中 所以单独自己调 而没有走ProductListServiceImpl的代码
|
|
|
final String indexName = ISearchConstants.INDEX_NAME_PRODUCT_INDEX;
|
|
|
JSONObject jsonObject = searchCacheService.getJSONObjectFromCache(indexName, productIndexSearchParam);
|
|
|
if (jsonObject != null) {
|
|
|
CACHE_MATCH_REQUEST.info("match cache for product list search recommendation by skns, sknList = {}.", sknList);
|
|
|
return jsonObject.getString("aggKeyword");
|
|
|
}
|
|
|
|
|
|
SearchResult productIndexSearchResult = searchCommonService.doSearch(indexName, productIndexSearchParam);
|
|
|
if (productIndexSearchResult == null) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
List<Map<String, Object>> productList = productIndexSearchResult.getResultList().stream().map(map -> searchServiceHelper.getProductMap(map)).collect(Collectors.toList());
|
|
|
String aggKeyword = aggKeywordsByProductList(productList);
|
|
|
jsonObject = new JSONObject();
|
|
|
jsonObject.put("aggKeyword", aggKeyword);
|
|
|
return aggKeyword;
|
|
|
}
|
|
|
|
|
|
private boolean isQuerySkn(String queryWord) {
|
|
|
String checkStr = queryWord.toLowerCase().trim();
|
|
|
if (checkStr.startsWith("skn")) {
|
|
|
checkStr = checkStr.substring("skn".length()).trim();
|
|
|
}
|
|
|
return searchCommonHelper.isQuerySkn(checkStr);
|
|
|
}
|
|
|
|
|
|
private JSONObject defaultSuggestRecommendation() {
|
|
|
JSONObject emptySuggestResult = new JSONObject();
|
|
|
emptySuggestResult.put("terms_suggestion", DEFAULT_SUGGEST_TIPS);
|
|
|
return emptySuggestResult;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 根据搜索的关键词到conversion索引查询关联的关键词。
|
|
|
* 该索引包含了爬虫提取的关键词、品牌的关键词定义和自定义的关键词。
|
|
|
*
|
|
|
* @param queryWord 搜索的关键词
|
|
|
* @return 推荐关键词列表
|
|
|
*/
|
|
|
private String getSuggestConversionDestBySource(String queryWord) {
|
|
|
long begin = System.currentTimeMillis();
|
|
|
logger.info("[func=getSuggestConversionDestBySource][query={}][begin={}]", queryWord, begin);
|
|
|
|
|
|
// 1) 先检查自定义的配置
|
|
|
if (CUSTOM_SUGGEST_CONVERSIONS.containsKey(queryWord.trim().toLowerCase())) {
|
|
|
return CUSTOM_SUGGEST_CONVERSIONS.get(queryWord.trim().toLowerCase());
|
|
|
}
|
|
|
|
|
|
// 2) 根据terms搜索构造搜索请求 要求完全匹配source
|
|
|
SearchParam searchParam = new SearchParam();
|
|
|
searchParam.setQuery(QueryBuilders.termQuery("source.source_keyword", queryWord.trim().toLowerCase()));
|
|
|
searchParam.setPage(1);
|
|
|
searchParam.setSize(1);
|
|
|
searchParam.setFiter(QueryBuilders.termQuery("status", 1));
|
|
|
|
|
|
// 3) 先从缓存中获取
|
|
|
final String indexName = ISearchConstants.INDEX_NAME_CONVERSION;
|
|
|
JSONObject suggestResult = searchCacheService.getJSONObjectFromCache(indexName, searchParam);
|
|
|
if (suggestResult != null) {
|
|
|
CACHE_MATCH_REQUEST.info("match cache for product list search recommendation by conversion, keyword = {}.", queryWord);
|
|
|
return suggestResult.getString("dest");
|
|
|
}
|
|
|
|
|
|
// 4) 调用ES执行搜索
|
|
|
SearchResult searchResult = searchCommonService.doSearch(indexName, searchParam);
|
|
|
if (searchResult == null) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
String dest = "";
|
|
|
if (CollectionUtils.isNotEmpty(searchResult.getResultList())) {
|
|
|
// 5) 从conversion获取的keyword列表
|
|
|
String source = (String) searchResult.getResultList().get(0).get("source");
|
|
|
dest = (String) searchResult.getResultList().get(0).get("dest");
|
|
|
logger.info("[func=getSuggestConversionDestBySource][source={}][dest={}]", source, dest);
|
|
|
}
|
|
|
|
|
|
// 6) 加入缓存
|
|
|
suggestResult = new JSONObject();
|
|
|
suggestResult.put("dest", dest);
|
|
|
searchCacheService.addJSONObjectToCache(indexName, searchParam, suggestResult);
|
|
|
logger.info("[func=getSuggestConversionDestBySource][query={}][cost={}]", queryWord, System.currentTimeMillis() - begin);
|
|
|
return dest;
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
* 根据关键词和时间查询关键词转换关系
|
|
|
*
|
|
|
* @param paramMap 查询参数
|
|
|
* @return 满足查询要求的转换关系
|
|
|
*/
|
|
|
@Override
|
|
|
public SearchApiResult suggestConversionList(Map<String, String> paramMap) {
|
|
|
try {
|
|
|
long begin = System.currentTimeMillis();
|
|
|
logger.info("[func=suggestConversionList][param={}][begin={}]", paramMap, begin);
|
|
|
|
|
|
String queryWord = paramMap.get("query");
|
|
|
int updateTime = StringUtils.isBlank(paramMap.get("updateTime")) ? 0 : Integer.parseInt(paramMap.get("updateTime"));
|
|
|
int page = StringUtils.isBlank(paramMap.get("page")) ? 1 : Integer.parseInt(paramMap.get("page"));
|
|
|
if (page < 1) {
|
|
|
throw new IllegalArgumentException("分页参数不合法");
|
|
|
}
|
|
|
|
|
|
// 1) 构建ES请求
|
|
|
SearchParam searchParam = new SearchParam();
|
|
|
QueryBuilder queryBuilder = StringUtils.isNotEmpty(queryWord) ? QueryBuilders.matchQuery("source", queryWord) : QueryBuilders.matchAllQuery();
|
|
|
searchParam.setQuery(queryBuilder);
|
|
|
searchParam.setPage(page);
|
|
|
searchParam.setOffset((page - 1) * 10);
|
|
|
searchParam.setSize(10);
|
|
|
if (updateTime > 0) {
|
|
|
searchParam.setFiter(QueryBuilders.rangeQuery("updateTime").gte(updateTime));
|
|
|
}
|
|
|
|
|
|
if (StringUtils.isEmpty(queryWord)) {
|
|
|
// 没有传入query时按照时间降序排序
|
|
|
searchParam.setSortBuilders(Arrays.asList(SortBuilders.fieldSort("updateTime").order(SortOrder.DESC)));
|
|
|
}
|
|
|
|
|
|
// 2) 调用ES查询
|
|
|
SearchResult searchResult = searchCommonService.doSearch(ISearchConstants.INDEX_NAME_CONVERSION, searchParam);
|
|
|
if (searchResult == null) {
|
|
|
return null;
|
|
|
}
|
|
|
|
|
|
// 3) 返回结果
|
|
|
Map<String, Object> dataMap = new HashMap<>();
|
|
|
dataMap.put("total", searchResult.getTotal());
|
|
|
dataMap.put("page", searchResult.getPage());
|
|
|
dataMap.put("page_size", searchParam.getSize());
|
|
|
dataMap.put("page_total", searchResult.getTotalPage());
|
|
|
dataMap.put("conversion_list", searchResult.getResultList());
|
|
|
logger.info("[func=suggestConversionList][cost={}]", System.currentTimeMillis() - begin);
|
|
|
return new SearchApiResult().setData(dataMap);
|
|
|
} catch (Exception e) {
|
|
|
logger.error(e.getMessage(), e);
|
|
|
return new SearchApiResult().setCode(500).setMessage(e.getMessage()).setData(null);
|
|
|
}
|
|
|
}
|
|
|
} |
...
|
...
|
|