|
|
1
|
+package com.yoho.search.service.servicenew.impl;
|
|
|
2
|
+
|
|
|
3
|
+import java.io.UnsupportedEncodingException;
|
|
|
4
|
+import java.net.URLDecoder;
|
|
|
5
|
+import java.util.ArrayList;
|
|
|
6
|
+import java.util.Collections;
|
|
|
7
|
+import java.util.Comparator;
|
|
|
8
|
+import java.util.HashMap;
|
|
|
9
|
+import java.util.Iterator;
|
|
|
10
|
+import java.util.List;
|
|
|
11
|
+import java.util.Map;
|
|
|
12
|
+
|
|
|
13
|
+import org.apache.commons.lang.StringUtils;
|
|
|
14
|
+import org.elasticsearch.index.query.BoolQueryBuilder;
|
|
|
15
|
+import org.elasticsearch.index.query.MatchQueryBuilder;
|
|
|
16
|
+import org.elasticsearch.index.query.MultiMatchQueryBuilder;
|
|
|
17
|
+import org.elasticsearch.index.query.QueryBuilders;
|
|
|
18
|
+import org.elasticsearch.index.query.functionscore.FunctionScoreQueryBuilder;
|
|
|
19
|
+import org.elasticsearch.index.query.functionscore.ScoreFunctionBuilders;
|
|
|
20
|
+import org.elasticsearch.search.SearchHit;
|
|
|
21
|
+import org.elasticsearch.search.SearchHits;
|
|
|
22
|
+import org.elasticsearch.search.aggregations.AbstractAggregationBuilder;
|
|
|
23
|
+import org.elasticsearch.search.aggregations.Aggregation;
|
|
|
24
|
+import org.elasticsearch.search.aggregations.AggregationBuilders;
|
|
|
25
|
+import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
|
|
|
26
|
+import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation.Bucket;
|
|
|
27
|
+import org.elasticsearch.search.aggregations.bucket.terms.Terms;
|
|
|
28
|
+import org.elasticsearch.search.aggregations.bucket.terms.TermsBuilder;
|
|
|
29
|
+import org.elasticsearch.search.aggregations.metrics.tophits.TopHits;
|
|
|
30
|
+import org.elasticsearch.search.sort.SortBuilders;
|
|
|
31
|
+import org.elasticsearch.search.sort.SortOrder;
|
|
|
32
|
+import org.slf4j.Logger;
|
|
|
33
|
+import org.slf4j.LoggerFactory;
|
|
|
34
|
+import org.springframework.beans.factory.annotation.Autowired;
|
|
|
35
|
+import org.springframework.stereotype.Service;
|
|
|
36
|
+
|
|
|
37
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
38
|
+import com.yoho.search.base.utils.ISearchConstants;
|
|
|
39
|
+import com.yoho.search.core.es.model.SearchParam;
|
|
|
40
|
+import com.yoho.search.core.es.model.SearchResult;
|
|
|
41
|
+import com.yoho.search.service.service.SearchCacheService;
|
|
|
42
|
+import com.yoho.search.service.service.SearchCommonService;
|
|
|
43
|
+import com.yoho.search.service.service.base.BrandIndexBaseService;
|
|
|
44
|
+import com.yoho.search.service.service.base.ShopsIndexBaseService;
|
|
|
45
|
+import com.yoho.search.service.service.helper.SearchCommonHelper;
|
|
|
46
|
+import com.yoho.search.service.service.helper.SearchServiceHelper;
|
|
|
47
|
+import com.yoho.search.service.servicenew.IShopListService;
|
|
|
48
|
+import com.yoho.search.service.utils.SearchRequestParams;
|
|
|
49
|
+import com.yoho.search.service.vo.SearchApiResult;
|
|
|
50
|
+import com.yoho.search.service.vo.SearchSort;
|
|
|
51
|
+
|
|
|
52
|
+@Service
|
|
|
53
|
+public class ShopListServiceImpl implements IShopListService {
|
|
|
54
|
+
|
|
|
55
|
+ @Autowired
|
|
|
56
|
+ private SearchCacheService searchCacheService;
|
|
|
57
|
+ @Autowired
|
|
|
58
|
+ private SearchCommonService searchCommonService;
|
|
|
59
|
+ @Autowired
|
|
|
60
|
+ private SearchServiceHelper searchServiceHelper;
|
|
|
61
|
+ @Autowired
|
|
|
62
|
+ private ShopsIndexBaseService shopsIndexBaseService;
|
|
|
63
|
+ @Autowired
|
|
|
64
|
+ private BrandIndexBaseService brandIndexBaseService;
|
|
|
65
|
+ @Autowired
|
|
|
66
|
+ private SearchCommonHelper searchCommonHelper;
|
|
|
67
|
+
|
|
|
68
|
+ private static final Logger logger = LoggerFactory.getLogger(ShopsServiceImpl.class);
|
|
|
69
|
+
|
|
|
70
|
+ private String getLegalKeyWord(Map<String, String> paramMap) {
|
|
|
71
|
+ String keyword = paramMap.get(SearchRequestParams.PARAM_SEARCH_SHOPS_KEYWORD);
|
|
|
72
|
+ if (StringUtils.isBlank(keyword)) {
|
|
|
73
|
+ return null;
|
|
|
74
|
+ }
|
|
|
75
|
+ if (keyword.contains("%")) {
|
|
|
76
|
+ keyword.replace("%", "percent");// 特殊处理
|
|
|
77
|
+ }
|
|
|
78
|
+ // 编码转换
|
|
|
79
|
+ String is_encode = paramMap.get("is_encode");
|
|
|
80
|
+ if (StringUtils.isNotBlank(is_encode) && is_encode.equals("1")) {
|
|
|
81
|
+ try {
|
|
|
82
|
+ keyword = URLDecoder.decode(keyword, "UTF-8");
|
|
|
83
|
+ } catch (UnsupportedEncodingException e) {
|
|
|
84
|
+ logger.warn(e.getMessage(), e);
|
|
|
85
|
+ }
|
|
|
86
|
+ }
|
|
|
87
|
+ return keyword;
|
|
|
88
|
+ }
|
|
|
89
|
+
|
|
|
90
|
+ @Override
|
|
|
91
|
+ public SearchApiResult searchShopList(Map<String, String> paramMap) {
|
|
|
92
|
+ logger.info("[func=searchShops][param={}][begin={}]", paramMap.toString(), System.currentTimeMillis());
|
|
|
93
|
+ // 1、获取搜索店铺的关键词
|
|
|
94
|
+ String keyword = this.getLegalKeyWord(paramMap);
|
|
|
95
|
+ if (StringUtils.isBlank(keyword)) {
|
|
|
96
|
+ return new SearchApiResult().setCode(400).setMessage("请传keyword");
|
|
|
97
|
+ }
|
|
|
98
|
+ SearchParam searchParam = new SearchParam();
|
|
|
99
|
+ searchParam.setSize(0);
|
|
|
100
|
+
|
|
|
101
|
+ // 2、构建query
|
|
|
102
|
+ MultiMatchQueryBuilder queryBuilder = QueryBuilders.multiMatchQuery(keyword);
|
|
|
103
|
+ queryBuilder.operator(MatchQueryBuilder.Operator.OR);
|
|
|
104
|
+ StringBuilder searchField = new StringBuilder();
|
|
|
105
|
+ searchField.append("brandName.brandName_lowercase^4000,brandName^900").append(",");
|
|
|
106
|
+ searchField.append("shopName.shopName_lowercase^4000,shopName^900").append(",");
|
|
|
107
|
+ searchField.append("brandNameCn^850").append(",").append("brandNameCn.brandNameCn_pinyin^850").append(",");
|
|
|
108
|
+ searchField.append("brandNameEn^800");
|
|
|
109
|
+ String[] fields = searchField.toString().split(",");
|
|
|
110
|
+ for (String field : fields) {
|
|
|
111
|
+ String[] fieldBoost = field.split("\\^");
|
|
|
112
|
+ if (fieldBoost.length == 2) {
|
|
|
113
|
+ queryBuilder.field(fieldBoost[0], Float.parseFloat(fieldBoost[1]));
|
|
|
114
|
+ } else if (fieldBoost.length == 1) {
|
|
|
115
|
+ queryBuilder.field(fieldBoost[0]);
|
|
|
116
|
+ }
|
|
|
117
|
+ }
|
|
|
118
|
+ queryBuilder.minimumShouldMatch("95%");
|
|
|
119
|
+ // 2.1 全球购得分减半
|
|
|
120
|
+ FunctionScoreQueryBuilder functionScoreQueryBuilder = new FunctionScoreQueryBuilder(queryBuilder);
|
|
|
121
|
+ functionScoreQueryBuilder.add(QueryBuilders.termQuery("isGlobal", "Y"), ScoreFunctionBuilders.weightFactorFunction(0.5f));
|
|
|
122
|
+ searchParam.setQuery(functionScoreQueryBuilder);
|
|
|
123
|
+
|
|
|
124
|
+ // 3、构建filter
|
|
|
125
|
+ // 3.1默认条件
|
|
|
126
|
+ BoolQueryBuilder boolFilter = QueryBuilders.boolQuery();
|
|
|
127
|
+ boolFilter.must(QueryBuilders.termQuery("status", 1));
|
|
|
128
|
+ boolFilter.must(QueryBuilders.rangeQuery("storageNum").gte(1));
|
|
|
129
|
+ boolFilter.must(QueryBuilders.termQuery("isOutlets", 2));
|
|
|
130
|
+ boolFilter.must(QueryBuilders.termQuery("attribute", 1));
|
|
|
131
|
+ boolFilter.mustNot(QueryBuilders.termsQuery("isSeckill", "Y"));
|
|
|
132
|
+
|
|
|
133
|
+ // 3.2店铺过滤器
|
|
|
134
|
+ BoolQueryBuilder shopFilter = QueryBuilders.boolQuery();
|
|
|
135
|
+ // 3.2.1 全球购店铺过滤器
|
|
|
136
|
+ BoolQueryBuilder globalShopFilter = QueryBuilders.boolQuery();
|
|
|
137
|
+ globalShopFilter.must(QueryBuilders.termsQuery("isGlobal", "Y"));
|
|
|
138
|
+ globalShopFilter.must(QueryBuilders.termQuery("shopId", "0"));
|
|
|
139
|
+ // 3.2.2 有货店铺过滤器
|
|
|
140
|
+ BoolQueryBuilder yohoShopFilter = QueryBuilders.boolQuery();
|
|
|
141
|
+ yohoShopFilter.mustNot(QueryBuilders.termsQuery("isGlobal", "Y"));
|
|
|
142
|
+ yohoShopFilter.must(QueryBuilders.rangeQuery("shopId").gt(0));
|
|
|
143
|
+ // 判断是否需要包含全球购
|
|
|
144
|
+ if (!searchCommonHelper.containGlobal(paramMap)) {
|
|
|
145
|
+ shopFilter.must(yohoShopFilter);
|
|
|
146
|
+ } else {
|
|
|
147
|
+ shopFilter.should(yohoShopFilter);
|
|
|
148
|
+ shopFilter.should(globalShopFilter);
|
|
|
149
|
+ }
|
|
|
150
|
+ boolFilter.must(shopFilter);
|
|
|
151
|
+ searchParam.setFiter(boolFilter);
|
|
|
152
|
+
|
|
|
153
|
+ // 4、构建聚合条件
|
|
|
154
|
+ final String firstAggName = "firstAgg";
|
|
|
155
|
+ SearchSort aggSort = new SearchSort("_score", SortOrder.DESC);
|
|
|
156
|
+ List<AbstractAggregationBuilder> list = new ArrayList<AbstractAggregationBuilder>();
|
|
|
157
|
+ // 2.1)构造父聚合:品牌或品类聚合【同时按子聚合的sort字段排序】
|
|
|
158
|
+ TermsBuilder firstAggregationBuilder = AggregationBuilders.terms(firstAggName).field("shopId").order(Terms.Order.aggregation("sort", aggSort.asc())).size(50);
|
|
|
159
|
+ // 2.2)添加子聚合:取得分最大的值
|
|
|
160
|
+ firstAggregationBuilder.subAggregation(AggregationBuilders.max("sort").field(aggSort.getSortField()));
|
|
|
161
|
+ // 2.3)添加孙聚合:取打分最高的一个product
|
|
|
162
|
+ firstAggregationBuilder.subAggregation(AggregationBuilders.topHits("product").addSort(SortBuilders.fieldSort(aggSort.getSortField()).order(aggSort.getSortOrder()))
|
|
|
163
|
+ .setSize(1));
|
|
|
164
|
+ list.add(firstAggregationBuilder);
|
|
|
165
|
+ searchParam.setAggregationBuilders(list);
|
|
|
166
|
+
|
|
|
167
|
+ // 5、根据searchParam查询ES
|
|
|
168
|
+ // 3、先从缓存中获取,如果能取到,则直接返回
|
|
|
169
|
+ JSONObject jsonObject = searchCacheService.getJSONObjectFromCache(ISearchConstants.INDEX_NAME_PRODUCT_INDEX, searchParam);
|
|
|
170
|
+ if (jsonObject != null) {
|
|
|
171
|
+ return new SearchApiResult().setData(jsonObject);
|
|
|
172
|
+ }
|
|
|
173
|
+ // 4、执行搜索,并构造返回结果
|
|
|
174
|
+ final String indexName = ISearchConstants.INDEX_NAME_PRODUCT_INDEX;
|
|
|
175
|
+ SearchResult searchResult = searchCommonService.doSearch(indexName, searchParam);
|
|
|
176
|
+ if (searchResult == null || searchResult.getAggMaps() == null) {
|
|
|
177
|
+ return null;
|
|
|
178
|
+ }
|
|
|
179
|
+ Map<String, Aggregation> aggMaps = searchResult.getAggMaps();
|
|
|
180
|
+ if (!aggMaps.containsKey(firstAggName)) {
|
|
|
181
|
+ return null;
|
|
|
182
|
+ }
|
|
|
183
|
+ // 5、构造返回结果
|
|
|
184
|
+ List<Map<String, Object>> shop_list = this.getShopList(((MultiBucketsAggregation) aggMaps.get(firstAggName)));
|
|
|
185
|
+ jsonObject = new JSONObject();
|
|
|
186
|
+ jsonObject.put("shop_list", shop_list);
|
|
|
187
|
+ searchCacheService.addJSONObjectToCache(indexName, searchParam, jsonObject);
|
|
|
188
|
+ return new SearchApiResult().setData(jsonObject);
|
|
|
189
|
+ }
|
|
|
190
|
+
|
|
|
191
|
+ /**
|
|
|
192
|
+ * 从聚合结果中获取原生的商品列表
|
|
|
193
|
+ *
|
|
|
194
|
+ * @param aggregation
|
|
|
195
|
+ * @return
|
|
|
196
|
+ */
|
|
|
197
|
+ private List<Map<String, Object>> getShopList(final MultiBucketsAggregation aggregation) {
|
|
|
198
|
+ Iterator<? extends Bucket> itAgg = aggregation.getBuckets().iterator();
|
|
|
199
|
+ List<Map<String, Object>> productList = new ArrayList<Map<String, Object>>();
|
|
|
200
|
+ while (itAgg.hasNext()) {
|
|
|
201
|
+ Bucket lt = itAgg.next();
|
|
|
202
|
+ if (lt.getAggregations().getAsMap().containsKey("product")) {
|
|
|
203
|
+ TopHits topHits = lt.getAggregations().get("product");
|
|
|
204
|
+ if (topHits != null) {
|
|
|
205
|
+ SearchHits hits = topHits.getHits();
|
|
|
206
|
+ for (SearchHit hit : hits.getHits()) {
|
|
|
207
|
+ Map<String, Object> source = hit.getSource();
|
|
|
208
|
+ float _score = hit.getScore();
|
|
|
209
|
+ source.put("_score", _score);
|
|
|
210
|
+ productList.add(source);
|
|
|
211
|
+ }
|
|
|
212
|
+ }
|
|
|
213
|
+ }
|
|
|
214
|
+ }
|
|
|
215
|
+ if (productList == null || productList.isEmpty()) {
|
|
|
216
|
+ return new ArrayList<Map<String, Object>>();
|
|
|
217
|
+ }
|
|
|
218
|
+ productList = this.sortListBySortField(productList);
|
|
|
219
|
+
|
|
|
220
|
+ List<Integer> yohoShopIds = new ArrayList<Integer>();
|
|
|
221
|
+ List<Integer> globalBrandIds = new ArrayList<Integer>();
|
|
|
222
|
+
|
|
|
223
|
+ for (Map<String, Object> map : productList) {
|
|
|
224
|
+ Integer shopId = (Integer) map.get("shopId");
|
|
|
225
|
+ Integer brandId = (Integer) map.get("brandId");
|
|
|
226
|
+ String isGlobal = (String) map.get("isGlobal");
|
|
|
227
|
+ if ("Y".equals(isGlobal) && brandId != null) {
|
|
|
228
|
+ globalBrandIds.add(brandId);
|
|
|
229
|
+ }
|
|
|
230
|
+ if ("N".equals(isGlobal) && shopId != null && shopId > 0) {
|
|
|
231
|
+ yohoShopIds.add(shopId);
|
|
|
232
|
+ }
|
|
|
233
|
+ }
|
|
|
234
|
+ Map<String, Map<String, Object>> yohoShopMap = shopsIndexBaseService.getShopsMapByIds(yohoShopIds);
|
|
|
235
|
+ Map<String, Map<String, Object>> globalBrandMap = brandIndexBaseService.getGlobalBrandMapByIds(globalBrandIds);
|
|
|
236
|
+
|
|
|
237
|
+ List<Map<String, Object>> shops_info = new ArrayList<Map<String, Object>>();
|
|
|
238
|
+ for (Map<String, Object> map : productList) {
|
|
|
239
|
+ String shopId = map.get("shopId") == null ? "" : map.get("shopId").toString();
|
|
|
240
|
+ String brandId = map.get("brandId") == null ? "" : map.get("brandId").toString();
|
|
|
241
|
+ String isGlobal = (String) map.get("isGlobal");
|
|
|
242
|
+ Map<String, Object> shop_info = new HashMap<String, Object>();
|
|
|
243
|
+ if ("Y".equals(isGlobal) && brandId != null) {
|
|
|
244
|
+ Map<String, Object> globalBrand = globalBrandMap.get(brandId);
|
|
|
245
|
+ if (globalBrand != null) {
|
|
|
246
|
+ shop_info.put("tbl_brand", globalBrand);
|
|
|
247
|
+ shop_info.put("yoho_shop", null);
|
|
|
248
|
+ shops_info.add(shop_info);
|
|
|
249
|
+ }
|
|
|
250
|
+ }
|
|
|
251
|
+ if ("N".equals(isGlobal) && shopId != null && Integer.valueOf(shopId) > 0) {
|
|
|
252
|
+ Map<String, Object> yohoShopInfo = yohoShopMap.get(shopId);
|
|
|
253
|
+ if (yohoShopInfo != null) {
|
|
|
254
|
+ shop_info.put("tbl_brand", null);
|
|
|
255
|
+ shop_info.put("yoho_shop", yohoShopInfo);
|
|
|
256
|
+ shops_info.add(shop_info);
|
|
|
257
|
+ }
|
|
|
258
|
+ }
|
|
|
259
|
+ }
|
|
|
260
|
+ return shops_info;
|
|
|
261
|
+ }
|
|
|
262
|
+
|
|
|
263
|
+ private double getDouble(Object value) {
|
|
|
264
|
+ if (value == null) {
|
|
|
265
|
+ return 0;
|
|
|
266
|
+ }
|
|
|
267
|
+ if (value instanceof Float) {
|
|
|
268
|
+ return ((Float) value).floatValue();
|
|
|
269
|
+ }
|
|
|
270
|
+ if (value instanceof Integer) {
|
|
|
271
|
+ return ((Integer) value);
|
|
|
272
|
+ }
|
|
|
273
|
+ if (value instanceof Long) {
|
|
|
274
|
+ return ((Long) value);
|
|
|
275
|
+ }
|
|
|
276
|
+ if (value instanceof String) {
|
|
|
277
|
+ return Double.valueOf(value.toString());
|
|
|
278
|
+ }
|
|
|
279
|
+ if (value instanceof Double) {
|
|
|
280
|
+ return Double.valueOf(value.toString());
|
|
|
281
|
+ }
|
|
|
282
|
+ return 0;
|
|
|
283
|
+ }
|
|
|
284
|
+
|
|
|
285
|
+ private List<Map<String, Object>> sortListBySortField(List<Map<String, Object>> productList) {
|
|
|
286
|
+ if (productList == null || productList.isEmpty()) {
|
|
|
287
|
+ return productList;
|
|
|
288
|
+ }
|
|
|
289
|
+ // 再按照某个字段对商品排序
|
|
|
290
|
+ boolean asc = false;
|
|
|
291
|
+ Collections.sort(productList, new Comparator<Map<String, Object>>() {
|
|
|
292
|
+ public int compare(Map<String, Object> o1, Map<String, Object> o2) {
|
|
|
293
|
+ try {
|
|
|
294
|
+ double value1 = getDouble(o1.get("_score"));
|
|
|
295
|
+ double value2 = getDouble(o2.get("_score"));
|
|
|
296
|
+ if (value1 == value2) {
|
|
|
297
|
+ return 0;
|
|
|
298
|
+ }
|
|
|
299
|
+ if (asc) {
|
|
|
300
|
+ return value1 - value2 > 0 ? 1 : -1;
|
|
|
301
|
+ } else {
|
|
|
302
|
+ return value1 - value2 > 0 ? -1 : 1;
|
|
|
303
|
+ }
|
|
|
304
|
+ } catch (Exception e) {
|
|
|
305
|
+ logger.error(e.getMessage(), e);
|
|
|
306
|
+ return -1;
|
|
|
307
|
+ }
|
|
|
308
|
+ }
|
|
|
309
|
+ });
|
|
|
310
|
+ return productList;
|
|
|
311
|
+ }
|
|
|
312
|
+
|
|
|
313
|
+} |