|
|
@@ -0,0 +1,1349 @@
|
|
|
+package com.uas.search.service.impl;
|
|
|
+
|
|
|
+import com.alibaba.fastjson.JSONObject;
|
|
|
+import com.uas.search.annotation.NotEmpty;
|
|
|
+import com.uas.search.constant.SearchConstants;
|
|
|
+import com.uas.search.constant.model.CollectField;
|
|
|
+import com.uas.search.constant.model.PageParams;
|
|
|
+import com.uas.search.constant.model.PageParams.FilterField;
|
|
|
+import com.uas.search.constant.model.SPage;
|
|
|
+import com.uas.search.exception.SearchException;
|
|
|
+import com.uas.search.grouping.DistinctGroupCollector;
|
|
|
+import com.uas.search.grouping.GoodsGroupCollector;
|
|
|
+import com.uas.search.model.*;
|
|
|
+import com.uas.search.service.SearchService;
|
|
|
+import com.uas.search.sort.SimilarValuesFieldComparatorSource;
|
|
|
+import com.uas.search.sort.StringFieldComparatorSource;
|
|
|
+import com.uas.search.util.DocumentToObjectUtils;
|
|
|
+import com.uas.search.util.SearchUtils;
|
|
|
+import org.apache.lucene.document.Document;
|
|
|
+import org.apache.lucene.index.Term;
|
|
|
+import org.apache.lucene.search.*;
|
|
|
+import org.apache.lucene.search.BooleanClause.Occur;
|
|
|
+import org.apache.lucene.search.SortField.Type;
|
|
|
+import org.slf4j.Logger;
|
|
|
+import org.slf4j.LoggerFactory;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import com.uas.search.util.CollectionUtils;
|
|
|
+import com.uas.search.util.StringUtils;
|
|
|
+
|
|
|
+import java.io.IOException;
|
|
|
+import java.net.URLDecoder;
|
|
|
+import java.util.*;
|
|
|
+import java.util.Map.Entry;
|
|
|
+
|
|
|
+/**
|
|
|
+ * 搜索索引
|
|
|
+ *
|
|
|
+ * @author sunyj
|
|
|
+ * @since 2016年8月5日 下午2:21:26
|
|
|
+ */
|
|
|
+@Service
|
|
|
+public class SearchServiceImpl implements SearchService {
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取联想词时返回的最大数目
|
|
|
+ */
|
|
|
+ private static final int SIMILAR_NUM = 20;
|
|
|
+
|
|
|
+ private static Logger logger = LoggerFactory.getLogger(SearchServiceImpl.class);
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SPage<Long> getKindIds(String keyword, Integer page, Integer size) {
|
|
|
+ List<Long> ids = new ArrayList<>();
|
|
|
+ SPage<Document> documents = getKindDocuments(keyword, page, size);;
|
|
|
+ SPage<Long> sPage = new SPage<Long>(documents.getTotalPage(), documents.getTotalElement(), documents.getPage(),
|
|
|
+ documents.getSize(), documents.isFirst(), documents.isLast());
|
|
|
+ for (Document document : documents.getContent()) {
|
|
|
+ ids.add(Long.parseLong(document.get(SearchConstants.KIND_ID_FIELD)));
|
|
|
+ }
|
|
|
+ sPage.setContent(ids);
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SPage<Map<String, Object>> getKinds(String keyword, Integer page, Integer size) {
|
|
|
+ List<Map<String, Object>> kinds = new ArrayList<>();
|
|
|
+ SPage<Document> documents = getKindDocuments(keyword, page, size);
|
|
|
+ SPage<Map<String, Object>> sPage = new SPage<>(documents.getTotalPage(),
|
|
|
+ documents.getTotalElement(), documents.getPage(), documents.getSize(), documents.isFirst(),
|
|
|
+ documents.isLast());
|
|
|
+ for (Document document : documents.getContent()) {
|
|
|
+ Map<String, Object> kind = new HashMap<>();
|
|
|
+ kind.put("id", Long.parseLong(document.get(SearchConstants.KIND_ID_FIELD)));
|
|
|
+ kind.put("nameCn", document.get(SearchConstants.KIND_NAMECN_FIELD));
|
|
|
+ kinds.add(kind);
|
|
|
+ }
|
|
|
+ sPage.setContent(kinds);
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ private SPage<Document> getKindDocuments(String keyword, Integer page, Integer size){
|
|
|
+ if (SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
+ throw new SearchException("搜索关键词无效:" + keyword);
|
|
|
+ }
|
|
|
+ BooleanQuery booleanQuery = SearchUtils.getBooleanQuery(SearchConstants.KIND_NAMECN_FIELD, keyword);
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+ return SearchUtils.getDocuments(SearchConstants.KIND_TABLE_NAME, booleanQuery, new Sort(sortKind()), page, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return 类目排序规则
|
|
|
+ */
|
|
|
+ private SortField[] sortKind() {
|
|
|
+ // 分数 > 访问量 > 搜索次数
|
|
|
+ return new SortField[]{
|
|
|
+ SortField.FIELD_SCORE,
|
|
|
+ sortField(SearchConstants.KIND_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.KIND_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE)
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 构造 SortField
|
|
|
+ *
|
|
|
+ * @param field Name of field to sort by. Can be <code>null</code> if
|
|
|
+ * <code>type</code> is SCORE or DOC.
|
|
|
+ * @param type Type of values in the terms.
|
|
|
+ * @param reverse True if natural order should be reversed.
|
|
|
+ * @param missingValue Used for 'sortMissingFirst/Last'
|
|
|
+ * @return SortField
|
|
|
+ */
|
|
|
+ private SortField sortField(String field, Type type, boolean reverse, Object missingValue) {
|
|
|
+ SortField sortField = new SortField(field, type, reverse);
|
|
|
+ sortField.setMissingValue(missingValue);
|
|
|
+ return sortField;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SPage<Long> getBrandIds(String keyword, Integer page, Integer size) {
|
|
|
+ List<Long> ids = new ArrayList<>();
|
|
|
+ SPage<Document> documents = getBrandDocuments(keyword, page, size);
|
|
|
+ SPage<Long> sPage = new SPage<Long>(documents.getTotalPage(), documents.getTotalElement(), documents.getPage(),
|
|
|
+ documents.getSize(), documents.isFirst(), documents.isLast());
|
|
|
+ for (Document document : documents.getContent()) {
|
|
|
+ ids.add(Long.parseLong(document.get(SearchConstants.BRAND_ID_FIELD)));
|
|
|
+ }
|
|
|
+ sPage.setContent(ids);
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SPage<Map<String, Object>> getBrands(String keyword, Integer page, Integer size) {
|
|
|
+ List<Map<String, Object>> brands = new ArrayList<>();
|
|
|
+ SPage<Document> documents = getBrandDocuments(keyword, page, size);
|
|
|
+ SPage<Map<String, Object>> sPage = new SPage<Map<String, Object>>(documents.getTotalPage(),
|
|
|
+ documents.getTotalElement(), documents.getPage(), documents.getSize(), documents.isFirst(),
|
|
|
+ documents.isLast());
|
|
|
+ for (Document document : documents.getContent()) {
|
|
|
+ Map<String, Object> brand = new HashMap<String, Object>();
|
|
|
+ brand.put("id", Long.parseLong(document.get(SearchConstants.BRAND_ID_FIELD)));
|
|
|
+ brand.put("uuid", document.get(SearchConstants.BRAND_UUID_FIELD));
|
|
|
+ brand.put("nameCn", document.get(SearchConstants.BRAND_NAMECN_FIELD));
|
|
|
+ brand.put("nameEn", document.get(SearchConstants.BRAND_NAMEEN_FIELD));
|
|
|
+ brands.add(brand);
|
|
|
+ }
|
|
|
+ sPage.setContent(brands);
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ private SPage<Document> getBrandDocuments(String keyword, Integer page, Integer size){
|
|
|
+ if (SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
+ throw new SearchException("搜索关键词无效:" + keyword);
|
|
|
+ }
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMECN_UNTOKENIZED_FIELD, keyword, true),
|
|
|
+ BooleanClause.Occur.SHOULD);
|
|
|
+ booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMEEN_UNTOKENIZED_FIELD, keyword, true),
|
|
|
+ BooleanClause.Occur.SHOULD);
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+ return SearchUtils.getDocuments(SearchConstants.BRAND_TABLE_NAME, booleanQuery, new Sort(sortBrand(keyword)), page,
|
|
|
+ size);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return 品牌排序规则
|
|
|
+ */
|
|
|
+ private SortField[] sortBrand(String keyword) {
|
|
|
+ // 自定义排序 > 权重 > 访问量 > 搜索次数 > 分数
|
|
|
+ // 分数排序放在最后,是因为有的中英文名称相同,分数翻倍,但实际匹配度并不高
|
|
|
+ return new SortField[]{
|
|
|
+ new SortField(SearchConstants.BRAND_NAMEEN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword)),
|
|
|
+ new SortField(SearchConstants.BRAND_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword)),
|
|
|
+ sortField(SearchConstants.BRAND_WEIGHT_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.BRAND_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.BRAND_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ SortField.FIELD_SCORE
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> getComponentIds(String keyword, PageParams pageParams) {
|
|
|
+ Map<String, Object> searchComponentIds = getComponentIds(keyword, pageParams, null, null);
|
|
|
+ return searchComponentIds;
|
|
|
+ // TODO 对品牌、类目甚至拼音混合搜索(待完善)
|
|
|
+ // int total = (int) searchComponentIds.get("total");
|
|
|
+ // if (total != 0) {
|
|
|
+ // return searchComponentIds;
|
|
|
+ // }
|
|
|
+ // List<Long> kindIds = getKindIds(keyword, Occur.SHOULD);
|
|
|
+ // List<Long> brandIds = getBrandIds(keyword, Occur.SHOULD);
|
|
|
+ // return getComponentIds(null, pageParams, kindIds, brandIds);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据关键词搜索产品
|
|
|
+ *
|
|
|
+ * @param keyword
|
|
|
+ * @param pageParams
|
|
|
+ * @param kindIds
|
|
|
+ * @param brandIds
|
|
|
+ * @return
|
|
|
+ * @throws SearchException
|
|
|
+ */
|
|
|
+ private Map<String, Object> getComponentIds(String keyword, PageParams pageParams, List<Long> kindIds,
|
|
|
+ List<Long> brandIds) throws SearchException {
|
|
|
+ // 因为器件、属性值的数据量远比类目、品牌大得多,而且器件搜索可能还需进行分页,
|
|
|
+ // 所以涉及器件、属性值的搜索,大都不能像类目和品牌一样直接利用SearchUtils.getDocuments方法
|
|
|
+ IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);
|
|
|
+
|
|
|
+ if (pageParams == null) {
|
|
|
+ pageParams = new PageParams();
|
|
|
+ }
|
|
|
+ if (pageParams.getPage() <= 0)
|
|
|
+ pageParams.setPage(1);
|
|
|
+ if (pageParams.getSize() <= 0)
|
|
|
+ pageParams.setSize(20);
|
|
|
+
|
|
|
+ Map<String, Object> map = new HashMap<String, Object>();
|
|
|
+ List<Long> ids = new ArrayList<Long>();
|
|
|
+ try {
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ if (!SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
+ booleanQuery.add(setBoost(keyword), BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<FilterField, Object> filters = pageParams.getFilters();
|
|
|
+ if (!CollectionUtils.isEmpty(filters)) {
|
|
|
+ // 筛选类目
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_KINDID))) {
|
|
|
+ String kindId = filters.get(FilterField.COMPONENT_KINDID).toString();
|
|
|
+ TermQuery kindQuery = new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, kindId));
|
|
|
+ booleanQuery.add(kindQuery, BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 筛选品牌
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_BRANDID))) {
|
|
|
+ String brandId = filters.get(FilterField.COMPONENT_BRANDID).toString();
|
|
|
+ TermQuery brandQuery = new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, brandId));
|
|
|
+ booleanQuery.add(brandQuery, BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 库存不为0
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_RESERVE))) {
|
|
|
+ Boolean isReserveNotEmpty = (Boolean) filters.get(FilterField.COMPONENT_HAS_RESERVE);
|
|
|
+ if (isReserveNotEmpty) {
|
|
|
+ booleanQuery.add(NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_RESERVE_FIELD, 0.0,
|
|
|
+ Double.MAX_VALUE, false, true), BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 现货、呆滞库存、样品数量不为0,取或的关系
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_SAMPLE))
|
|
|
+ || !StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_ORIGINAL))
|
|
|
+ || !StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_INACTION_STOCK))) {
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_SAMPLE))) {
|
|
|
+ booleanQuery2.add(NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_SAMPLE_QTY_FIELD,
|
|
|
+ 0.0, Double.MAX_VALUE, false, true), BooleanClause.Occur.SHOULD);
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_ORIGINAL))) {
|
|
|
+ booleanQuery2.add(NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_ORIGINAL_QTY_FIELD,
|
|
|
+ 0.0, Double.MAX_VALUE, false, true), BooleanClause.Occur.SHOULD);
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_INACTION_STOCK))) {
|
|
|
+ booleanQuery2.add(
|
|
|
+ NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_INACTION_STOCK_QTY_FIELD,
|
|
|
+ 0.0, Double.MAX_VALUE, false, true),
|
|
|
+ BooleanClause.Occur.SHOULD);
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, Occur.MUST);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 属性过滤
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_PROPERTIES))) {
|
|
|
+ JSONObject proJSON = JSONObject
|
|
|
+ .parseObject(String.valueOf(filters.get(FilterField.COMPONENT_PROPERTIES)));
|
|
|
+ for (String key : proJSON.keySet()) {
|
|
|
+ String value = String.valueOf(proJSON.get(key));
|
|
|
+ if (!StringUtils.isEmpty(value)) {
|
|
|
+ if (!key.startsWith(SearchConstants.COMPONENT_PROPERTY_PREFIX)) {
|
|
|
+ key = SearchConstants.COMPONENT_PROPERTY_PREFIX + key;
|
|
|
+ }
|
|
|
+ TermQuery propertyQuery = new TermQuery(new Term(key, value));
|
|
|
+ booleanQuery.add(propertyQuery, BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!CollectionUtils.isEmpty(kindIds)) {
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ for (Long id : kindIds) {
|
|
|
+ booleanQuery2.add(new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, id.toString())),
|
|
|
+ Occur.SHOULD);
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, Occur.MUST);
|
|
|
+ }
|
|
|
+ if (!CollectionUtils.isEmpty(brandIds)) {
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ for (Long id : brandIds) {
|
|
|
+ booleanQuery2.add(new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, id.toString())),
|
|
|
+ Occur.SHOULD);
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, Occur.MUST);
|
|
|
+ }
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+
|
|
|
+ Sort sort = new Sort(sortComponent());
|
|
|
+ TopDocs hits;
|
|
|
+ if (pageParams.getPage() > 1) {// 不是第一页
|
|
|
+ TopDocs previousHits = indexSearcher.search(booleanQuery,
|
|
|
+ (pageParams.getPage() - 1) * pageParams.getSize(), sort, true, false);
|
|
|
+ ScoreDoc[] previousScoreDocs = previousHits.scoreDocs;
|
|
|
+ ScoreDoc after = previousScoreDocs[previousScoreDocs.length - 1];
|
|
|
+ hits = indexSearcher.searchAfter(after, booleanQuery, pageParams.getSize(), sort, true, false);
|
|
|
+ } else {
|
|
|
+ hits = indexSearcher.search(booleanQuery, pageParams.getSize(), sort, true, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ ScoreDoc[] scoreDocs = hits.scoreDocs;
|
|
|
+ for (ScoreDoc scoreDoc : scoreDocs) {
|
|
|
+ // 数据量太大,需要指定将获取的数据(以免载入不必要的数据,降低速度)
|
|
|
+ Set<String> fieldsToLoad = new HashSet<>();
|
|
|
+ fieldsToLoad.add(SearchConstants.COMPONENT_ID_FIELD);
|
|
|
+ Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
|
|
|
+ String componentId = document.get(SearchConstants.COMPONENT_ID_FIELD);
|
|
|
+ ids.add(Long.parseLong(componentId));
|
|
|
+ // System.out.println(componentId + "\t" + scoreDoc.score);
|
|
|
+ }
|
|
|
+ map.put("componentIds", ids);
|
|
|
+ map.put("page", pageParams.getPage());
|
|
|
+ map.put("size", pageParams.getSize());
|
|
|
+ map.put("total", hits.totalHits);
|
|
|
+ } catch (IOException e) {
|
|
|
+ logger.error("", e);
|
|
|
+ } finally {
|
|
|
+ SearchUtils.releaseIndexSearcher(indexSearcher);
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同时搜索器件、类目、品牌,并设置boost
|
|
|
+ *
|
|
|
+ * @param keyword
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private Query setBoost(String keyword) {
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ PrefixQuery prefixQuery = new PrefixQuery(
|
|
|
+ new Term(SearchConstants.COMPONENT_CODE_FIELD, keyword.toLowerCase()));
|
|
|
+ prefixQuery.setBoost(100);
|
|
|
+ booleanQuery.add(prefixQuery, BooleanClause.Occur.SHOULD);
|
|
|
+ BooleanQuery brandNameCnQuery = SearchUtils.getBooleanQuery(SearchConstants.COMPONENT_BR_NAMECN_FIELD,
|
|
|
+ keyword);
|
|
|
+ brandNameCnQuery.setBoost(10);
|
|
|
+ booleanQuery.add(brandNameCnQuery, BooleanClause.Occur.SHOULD);
|
|
|
+ BooleanQuery brandNameEnQuery = SearchUtils.getBooleanQuery(SearchConstants.COMPONENT_BR_NAMEEN_FIELD,
|
|
|
+ keyword);
|
|
|
+ brandNameEnQuery.setBoost(10);
|
|
|
+ booleanQuery.add(brandNameEnQuery, BooleanClause.Occur.SHOULD);
|
|
|
+ BooleanQuery kindNameQuery = SearchUtils.getBooleanQuery(SearchConstants.COMPONENT_KI_NAME_FIELD, keyword);
|
|
|
+ kindNameQuery.setBoost(1);
|
|
|
+ booleanQuery.add(kindNameQuery, BooleanClause.Occur.SHOULD);
|
|
|
+ return booleanQuery;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return 器件排序规则
|
|
|
+ */
|
|
|
+ private SortField[] sortComponent() {
|
|
|
+ // 分数 > 器件(访问量 > 搜索次数) > 品牌(权重 > 访问量 > 搜索次数) > 类目(访问量 > 搜索次数)
|
|
|
+ return new SortField[]{
|
|
|
+ SortField.FIELD_SCORE,
|
|
|
+ sortField(SearchConstants.COMPONENT_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.COMPONENT_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.COMPONENT_BR_WEIGHT_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.COMPONENT_BR_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.COMPONENT_BR_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.COMPONENT_KI_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.COMPONENT_KI_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE)
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<Long> getKindIdsBySearchComponent(String keyword, String brandId) {
|
|
|
+ Query filter = null;
|
|
|
+ // 筛选品牌
|
|
|
+ if (!StringUtils.isEmpty(brandId)) {
|
|
|
+ filter = new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, brandId));
|
|
|
+ }
|
|
|
+ return collectBySearchComponent(keyword, filter, SearchConstants.COMPONENT_KI_ID_FIELD).getValues();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<Map<String, Object>> getKindsBySearchComponent(String keyword, String brandId) {
|
|
|
+ Query filter = null;
|
|
|
+ // 筛选品牌
|
|
|
+ if (!StringUtils.isEmpty(brandId)) {
|
|
|
+ filter = new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, brandId));
|
|
|
+ }
|
|
|
+ return collectBySearchComponent(keyword, filter, SearchConstants.KIND_ID_FIELD,
|
|
|
+ new DistinctGroupCollector.CollectField(SearchConstants.KIND_ID_FIELD, "id"),
|
|
|
+ new DistinctGroupCollector.CollectField(SearchConstants.KIND_NAMECN_FIELD, "nameCn"))
|
|
|
+ .getCollectValues();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<Long> getBrandIdsBySearchComponent(String keyword, String kindId) {
|
|
|
+ Query filter = null;
|
|
|
+ // 筛选类目
|
|
|
+ if (!StringUtils.isEmpty(kindId)) {
|
|
|
+ filter = new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, kindId));
|
|
|
+ }
|
|
|
+ return collectBySearchComponent(keyword, filter, SearchConstants.COMPONENT_BR_ID_FIELD).getValues();
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Set<Map<String, Object>> getBrandsBySearchComponent(String keyword, String kindId) {
|
|
|
+ Query filter = null;
|
|
|
+ // 筛选类目
|
|
|
+ if (!StringUtils.isEmpty(kindId)) {
|
|
|
+ filter = new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, kindId));
|
|
|
+ }
|
|
|
+ return collectBySearchComponent(keyword, filter, SearchConstants.COMPONENT_BR_ID_FIELD,
|
|
|
+ new DistinctGroupCollector.CollectField(SearchConstants.COMPONENT_BR_ID_FIELD, "id"),
|
|
|
+ new DistinctGroupCollector.CollectField(SearchConstants.COMPONENT_BR_UUID_FIELD, "uuid"),
|
|
|
+ new DistinctGroupCollector.CollectField(SearchConstants.COMPONENT_BR_NAMECN_FIELD, "nameCn"))
|
|
|
+ .getCollectValues();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 搜索器件时统计指定信息
|
|
|
+ *
|
|
|
+ * @param keyword 关键词
|
|
|
+ * @param filter 过滤条件
|
|
|
+ * @param groupField 统计的单个字段(多为 id)
|
|
|
+ * @param collectFields 统计的多个字段(详细信息),可为空
|
|
|
+ * @return 统计信息
|
|
|
+ */
|
|
|
+ private DistinctGroupCollector collectBySearchComponent(String keyword, Query filter, String groupField, DistinctGroupCollector.CollectField... collectFields){
|
|
|
+ if (SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
+ throw new SearchException("搜索关键词无效:" + keyword);
|
|
|
+ }
|
|
|
+ IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);
|
|
|
+ try {
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ keyword = URLDecoder.decode(keyword, "UTF-8");
|
|
|
+ booleanQuery.add(setBoost(keyword), BooleanClause.Occur.MUST);
|
|
|
+
|
|
|
+ if(filter != null){
|
|
|
+ booleanQuery.add(filter, Occur.FILTER);
|
|
|
+ }
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+
|
|
|
+ DistinctGroupCollector collector = new DistinctGroupCollector(groupField, collectFields);
|
|
|
+ indexSearcher.search(booleanQuery, collector);
|
|
|
+ return collector;
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new IllegalStateException("统计失败", e);
|
|
|
+ } finally {
|
|
|
+ SearchUtils.releaseIndexSearcher(indexSearcher);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<String> getSimilarKeywords(String keyword, Integer size) {
|
|
|
+ size = size == null || size < 1 ? SIMILAR_NUM : size;
|
|
|
+ List<String> result = new ArrayList<>();
|
|
|
+
|
|
|
+ // 相似的器件原厂型号数量足够,直接返回
|
|
|
+ List<String> componentCodes = getSimilarComponentCodes(keyword, size);
|
|
|
+ result.addAll(componentCodes);
|
|
|
+ removeDuplicate(result);
|
|
|
+ if (result.size() == size) {
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取相似类目
|
|
|
+ List<String> kindNames = getSimilarKindNames(keyword, size);
|
|
|
+ if (!CollectionUtils.isEmpty(kindNames)) {
|
|
|
+ result.addAll(kindNames);
|
|
|
+ removeDuplicate(result);
|
|
|
+ // 如果总的数量超出SIMILAR_NUM,去除多余的元素
|
|
|
+ if (result.size() > size) {
|
|
|
+ removeElements(result, size);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 获取相似品牌
|
|
|
+ List<String> brandNames = getSimilarBrandNames(keyword, size);
|
|
|
+ if (!CollectionUtils.isEmpty(brandNames)) {
|
|
|
+ result.addAll(brandNames);
|
|
|
+ removeDuplicate(result);
|
|
|
+ if (result.size() > size) {
|
|
|
+ removeElements(result, size);
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Map<String, Object>> getSimilarComponents(String componentCode, Integer size) {
|
|
|
+ size = size == null || size < 1 ? SIMILAR_NUM : size;
|
|
|
+ if (SearchUtils.isKeywordInvalid(componentCode)) {
|
|
|
+ throw new SearchException("输入无效:" + componentCode);
|
|
|
+ }
|
|
|
+ IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);
|
|
|
+
|
|
|
+ List<Map<String, Object>> components = new ArrayList<>();
|
|
|
+ try {
|
|
|
+ PrefixQuery prefixQuery = new PrefixQuery(
|
|
|
+ new Term(SearchConstants.COMPONENT_CODE_FIELD, componentCode.toLowerCase()));
|
|
|
+ logger.info(prefixQuery.toString());
|
|
|
+ TopDocs hits = indexSearcher.search(prefixQuery, size);
|
|
|
+ ScoreDoc[] scoreDocs = hits.scoreDocs;
|
|
|
+ for (ScoreDoc scoreDoc : scoreDocs) {
|
|
|
+ Set<String> fieldsToLoad = new HashSet<>();
|
|
|
+ fieldsToLoad.add(SearchConstants.COMPONENT_ID_FIELD);
|
|
|
+ fieldsToLoad.add(SearchConstants.COMPONENT_UUID_FIELD);
|
|
|
+ fieldsToLoad.add(SearchConstants.COMPONENT_CODE_FIELD);
|
|
|
+ Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
+ map.put("id", Long.parseLong(document.get(SearchConstants.COMPONENT_ID_FIELD)));
|
|
|
+ map.put("uuid", document.get(SearchConstants.COMPONENT_UUID_FIELD));
|
|
|
+ map.put("code", document.get(SearchConstants.COMPONENT_CODE_FIELD));
|
|
|
+ components.add(map);
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ logger.error("", e);
|
|
|
+ } finally {
|
|
|
+ SearchUtils.releaseIndexSearcher(indexSearcher);
|
|
|
+ }
|
|
|
+ return components;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Map<String, Object>> getSimilarBrands(String brandName, Integer size) {
|
|
|
+ size = size == null || size < 1 ? SIMILAR_NUM : size;
|
|
|
+ if (SearchUtils.isKeywordInvalid(brandName)) {
|
|
|
+ throw new SearchException("输入无效:" + brandName);
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> brands = new ArrayList<Map<String, Object>>();
|
|
|
+ // 品牌名称带有空格,并且中英文名并无一定顺序,因此对nameCn、nameEn均要搜索
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ // 搜索nameCn
|
|
|
+ booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMECN_FIELD, brandName),
|
|
|
+ BooleanClause.Occur.SHOULD);
|
|
|
+ // 搜索nameEn
|
|
|
+ booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMEEN_FIELD, brandName),
|
|
|
+ BooleanClause.Occur.SHOULD);
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+
|
|
|
+ List<Document> documents = SearchUtils.getDocuments(SearchConstants.BRAND_TABLE_NAME, booleanQuery, null, size)
|
|
|
+ .getContent();
|
|
|
+ for (Document document : documents) {
|
|
|
+ Map<String, Object> brand = new HashMap<>();
|
|
|
+ brand.put("id", Long.parseLong(document.get(SearchConstants.BRAND_ID_FIELD)));
|
|
|
+ brand.put("uuid", document.get(SearchConstants.BRAND_UUID_FIELD));
|
|
|
+ brand.put("nameCn", document.get(SearchConstants.BRAND_NAMECN_FIELD));
|
|
|
+ brand.put("nameEn", document.get(SearchConstants.BRAND_NAMEEN_FIELD));
|
|
|
+ brands.add(brand);
|
|
|
+ }
|
|
|
+ return brands;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Map<String, Object>> getSimilarKinds(String kindName, Integer size) {
|
|
|
+ size = size == null || size < 1 ? SIMILAR_NUM : size;
|
|
|
+ return getSimilarKinds(kindName, null, null, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Map<String, Object>> getSimilarLeafKinds(String kindName, Integer size) {
|
|
|
+ size = size == null || size < 1 ? SIMILAR_NUM : size;
|
|
|
+ return getSimilarKinds(kindName, (short) 1, null, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Map<String, Object>> getSimilarKindsByLevel(String kindName, Short level, Integer size) {
|
|
|
+ size = size == null || size < 1 ? SIMILAR_NUM : size;
|
|
|
+ return getSimilarKinds(kindName, null, level, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据输入的类目名获取联想词
|
|
|
+ *
|
|
|
+ * @param kindName
|
|
|
+ * 类目名
|
|
|
+ * @param isLeaf
|
|
|
+ * 是否只获取末级类目
|
|
|
+ * @param level
|
|
|
+ * 指定的类目级别
|
|
|
+ * @param size 指定的联想词数目
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> getSimilarKinds(String kindName, Short isLeaf, Short level, Integer size) {
|
|
|
+ size = size == null || size < 1 ? SIMILAR_NUM : size;
|
|
|
+ if (SearchUtils.isKeywordInvalid(kindName)) {
|
|
|
+ throw new SearchException("输入无效:" + kindName);
|
|
|
+ }
|
|
|
+ List<Map<String, Object>> kinds = new ArrayList<>();
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.KIND_NAMECN_FIELD, kindName),
|
|
|
+ BooleanClause.Occur.MUST);
|
|
|
+
|
|
|
+ if (isLeaf != null && isLeaf == 1) {
|
|
|
+ booleanQuery.add(new TermQuery(new Term(SearchConstants.KIND_ISLEAF_FIELD, String.valueOf(isLeaf))),
|
|
|
+ BooleanClause.Occur.MUST);
|
|
|
+ } else {
|
|
|
+ if (level != null && level > 0) {
|
|
|
+ booleanQuery.add(new TermQuery(new Term(SearchConstants.KIND_LEVEL_FIELD, String.valueOf(level))),
|
|
|
+ BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+ List<Document> documents = SearchUtils.getDocuments(SearchConstants.KIND_TABLE_NAME, booleanQuery, null, size).getContent();
|
|
|
+ for (Document document : documents) {
|
|
|
+ Map<String, Object> map = new HashMap<>();
|
|
|
+ map.put("id", Long.parseLong(document.get(SearchConstants.KIND_ID_FIELD)));
|
|
|
+ map.put("nameCn", document.get(SearchConstants.KIND_NAMECN_FIELD));
|
|
|
+ map.put("isLeaf", Short.parseShort(document.get(SearchConstants.KIND_ISLEAF_FIELD)));
|
|
|
+ map.put("level", Short.parseShort(document.get(SearchConstants.KIND_LEVEL_FIELD)));
|
|
|
+ kinds.add(map);
|
|
|
+ }
|
|
|
+ return kinds;
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Map<String, String>> getSimilarPropertyValues(Long kindId, Long propertyId, String keyword,
|
|
|
+ Long topNum) {
|
|
|
+ if (kindId == null || propertyId == null) {
|
|
|
+ throw new SearchException("类目id和属性id不能为空");
|
|
|
+ }
|
|
|
+ IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);
|
|
|
+
|
|
|
+ String propertyIdString = String.valueOf(propertyId);
|
|
|
+ if (!propertyIdString.startsWith(SearchConstants.COMPONENT_PROPERTY_PREFIX)) {
|
|
|
+ propertyIdString = SearchConstants.COMPONENT_PROPERTY_PREFIX + propertyIdString;
|
|
|
+ }
|
|
|
+ propertyIdString = propertyIdString + SearchConstants.COMPONENT_PROPERTY_TOKENIZED_SUFFIX;
|
|
|
+ if (keyword == null) {
|
|
|
+ keyword = "";
|
|
|
+ }
|
|
|
+ if (topNum == null || topNum < 1) {
|
|
|
+ topNum = (long) SIMILAR_NUM;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> propertyValues = new ArrayList<>();
|
|
|
+ try {
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ booleanQuery.add(new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, String.valueOf(kindId))),
|
|
|
+ BooleanClause.Occur.MUST);
|
|
|
+ booleanQuery.add(new PrefixQuery(new Term(propertyIdString, keyword.toLowerCase())),
|
|
|
+ BooleanClause.Occur.MUST);
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+ // 如果只搜索topNum个结果,去除重复的属性值后,数目很可能是不够的
|
|
|
+ TopDocs topDocs = indexSearcher.search(booleanQuery, SearchConstants.TOP_NUM);
|
|
|
+ ScoreDoc[] scoreDocs = topDocs.scoreDocs;
|
|
|
+ for (ScoreDoc scoreDoc : scoreDocs) {
|
|
|
+ Set<String> fieldsToLoad = new HashSet<>();
|
|
|
+ fieldsToLoad.add(propertyIdString);
|
|
|
+ Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
|
|
|
+ String propertyValue = document.get(propertyIdString);
|
|
|
+ if (!StringUtils.isEmpty(propertyValue) && !propertyValues.contains(propertyValue)) {
|
|
|
+ propertyValues.add(propertyValue);
|
|
|
+ }
|
|
|
+ if (propertyValues.size() >= topNum) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ logger.error("", e);
|
|
|
+ } finally {
|
|
|
+ SearchUtils.releaseIndexSearcher(indexSearcher);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<Map<String, String>> result = new ArrayList<>();
|
|
|
+ for (String propertyValue : propertyValues) {
|
|
|
+ Map<String, String> map = new HashMap<>();
|
|
|
+ map.put("propertyValue", propertyValue);
|
|
|
+ result.add(map);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据输入获取相似的器件原厂型号
|
|
|
+ *
|
|
|
+ * @param componentCode
|
|
|
+ * @param size 指定的联想词数目
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private List<String> getSimilarComponentCodes(String componentCode, Integer size) {
|
|
|
+ return getSimilarValues(SearchConstants.COMPONENT_TABLE_NAME, SearchConstants.COMPONENT_CODE_FIELD,
|
|
|
+ componentCode.toLowerCase(), QueryType.PREFIX_QUERY, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据输入获取相似的品牌名称
|
|
|
+ *
|
|
|
+ * @param brandName
|
|
|
+ * @param size 指定的联想词数目
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private List<String> getSimilarBrandNames(String brandName, Integer size) {
|
|
|
+ // 获取相似的中文品牌
|
|
|
+ List<String> nameCns = getSimilarValues(SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_NAMECN_FIELD,
|
|
|
+ brandName, QueryType.BOOLEAN_QUERY, size);
|
|
|
+ // 相似的中文品牌数量足够,直接返回
|
|
|
+ if (nameCns != null && nameCns.size() == SIMILAR_NUM) {
|
|
|
+ return nameCns;
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> names = nameCns;
|
|
|
+ // 获取相似的英文品牌
|
|
|
+ List<String> nameEns = getSimilarValues(SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_NAMEEN_FIELD,
|
|
|
+ brandName, QueryType.BOOLEAN_QUERY, size);
|
|
|
+ names.addAll(nameEns);
|
|
|
+ return names;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据输入获取相似的类目名称
|
|
|
+ *
|
|
|
+ * @param kindName
|
|
|
+ * @param size 指定的联想词数目
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private List<String> getSimilarKindNames(String kindName, Integer size) {
|
|
|
+ return getSimilarValues(SearchConstants.KIND_TABLE_NAME, SearchConstants.KIND_NAMECN_FIELD, kindName,
|
|
|
+ QueryType.BOOLEAN_QUERY, size);
|
|
|
+ }
|
|
|
+
|
|
|
+ private enum QueryType {
|
|
|
+ BOOLEAN_QUERY, PREFIX_QUERY
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 根据输入值获取该域相似的值
|
|
|
+ *
|
|
|
+ * @param tableName
|
|
|
+ * @param field
|
|
|
+ * @param keyword
|
|
|
+ * @param size 指定的联想词数目
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private List<String> getSimilarValues(String tableName, String field, String keyword, QueryType queryType, Integer size) {
|
|
|
+ if (SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
+ throw new SearchException("输入无效:" + keyword);
|
|
|
+ }
|
|
|
+ if (queryType == null) {
|
|
|
+ queryType = QueryType.BOOLEAN_QUERY;
|
|
|
+ }
|
|
|
+ IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(tableName);
|
|
|
+
|
|
|
+ List<String> result = new ArrayList<>();
|
|
|
+ try {
|
|
|
+ Query query = null;
|
|
|
+ if (queryType == QueryType.BOOLEAN_QUERY) {
|
|
|
+ query = SearchUtils.getBooleanQuery(field, keyword);
|
|
|
+ } else if (queryType == QueryType.PREFIX_QUERY) {
|
|
|
+ query = new PrefixQuery(new Term(field, keyword));
|
|
|
+ }
|
|
|
+ logger.info(query.toString());
|
|
|
+ Sort sort = new Sort(new SortField(field, new SimilarValuesFieldComparatorSource()));
|
|
|
+ TopDocs hits = indexSearcher.search(query, size, sort, true, false);
|
|
|
+ ScoreDoc[] scoreDocs = hits.scoreDocs;
|
|
|
+ for (ScoreDoc scoreDoc : scoreDocs) {
|
|
|
+ Set<String> fieldsToLoad = new HashSet<>();
|
|
|
+ fieldsToLoad.add(field);
|
|
|
+ Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
|
|
|
+ result.add(document.get(field));
|
|
|
+ // System.out.println(document.get(field) + "\t" +
|
|
|
+ // scoreDoc.score);
|
|
|
+ }
|
|
|
+ } catch (IOException e) {
|
|
|
+ logger.error("", e);
|
|
|
+ } finally {
|
|
|
+ SearchUtils.releaseIndexSearcher(indexSearcher);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 移除集合中重复的元素
|
|
|
+ *
|
|
|
+ * @param list
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private void removeDuplicate(List<String> list) {
|
|
|
+ if (list == null) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ List<String> result = new ArrayList<>();
|
|
|
+ for (String str : list) {
|
|
|
+ if (!result.contains(str)) {
|
|
|
+ result.add(str);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ list.removeAll(list);
|
|
|
+ list.addAll(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 删除collection内startIndex(含)后的元素
|
|
|
+ *
|
|
|
+ * @param collection
|
|
|
+ * @param startIndex
|
|
|
+ */
|
|
|
+ private void removeElements(Collection<? extends String> collection, int startIndex) {
|
|
|
+ if (CollectionUtils.isEmpty(collection)) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ int listsSize = collection.size();
|
|
|
+ for (int i = listsSize - 1; i >= startIndex; i--) {
|
|
|
+ collection.remove(i);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Map<String, Object> getGoodsIds(String keyword, PageParams pageParams) throws SearchException {
|
|
|
+ List<String> keywordFields = new ArrayList<>();
|
|
|
+ // 先根据品牌搜索,品牌不存在再搜索型号等
|
|
|
+ keywordFields.add(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD);
|
|
|
+ keywordFields.add(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD);
|
|
|
+ Map<String, Object> goodsIds = getGoodsIds(keyword, keywordFields, false, pageParams, false);
|
|
|
+ if (CollectionUtils.isEmpty(goodsIds) || goodsIds.get("componentIds") == null
|
|
|
+ || JSONObject.parseArray(goodsIds.get("componentIds").toString()).isEmpty()) {
|
|
|
+ goodsIds = getGoodsIds(keyword, null, true, pageParams, true);
|
|
|
+ }
|
|
|
+ return goodsIds;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param keyword
|
|
|
+ * @param keywordFields
|
|
|
+ * 要查询的字段
|
|
|
+ * @param tokenized
|
|
|
+ * 是否分词
|
|
|
+ * @param pageParams
|
|
|
+ * @param recursivelyGet 是否递归获取(逐步降低精度,直到只匹配一个字符)
|
|
|
+ * @return
|
|
|
+ * @throws SearchException
|
|
|
+ */
|
|
|
+ private Map<String, Object> getGoodsIds(String keyword, List<String> keywordFields, Boolean tokenized,
|
|
|
+ PageParams pageParams, Boolean recursivelyGet) throws SearchException {
|
|
|
+ // 因为器件、属性值的数据量远比类目、品牌大得多,而且器件搜索可能还需进行分页,
|
|
|
+ // 所以涉及器件、属性值的搜索,大都不能像类目和品牌一样直接利用SearchUtils.getDocuments方法
|
|
|
+ IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.GOODS_TABLE_NAME);
|
|
|
+
|
|
|
+ if (pageParams == null) {
|
|
|
+ pageParams = new PageParams();
|
|
|
+ }
|
|
|
+ if (pageParams.getPage() <= 0)
|
|
|
+ pageParams.setPage(1);
|
|
|
+ if (pageParams.getSize() <= 0)
|
|
|
+ pageParams.setSize(20);
|
|
|
+
|
|
|
+ Map<String, Object> map = new HashMap<String, Object>();
|
|
|
+ List<Long> cmpIds = new ArrayList<>();
|
|
|
+ List<Long> goIds = new ArrayList<>();
|
|
|
+ try {
|
|
|
+ BooleanQuery booleanQuery = queryGoods(keyword, keywordFields, tokenized);
|
|
|
+ Map<FilterField, Object> filters = pageParams.getFilters();
|
|
|
+ // 筛选状态
|
|
|
+ if (!CollectionUtils.isEmpty(filters) && !StringUtils.isEmpty(filters.get(FilterField.GOODS_STATUS))) {
|
|
|
+ filter(filters.get(FilterField.GOODS_STATUS), SearchConstants.GOODS_GO_STATUS_FIELD, booleanQuery);
|
|
|
+ } else {
|
|
|
+ // 未指定时,过滤默认状态
|
|
|
+ filter(Arrays.asList(TradeGoods.VALID_STATUS), SearchConstants.GOODS_GO_STATUS_FIELD, booleanQuery);
|
|
|
+ }
|
|
|
+ if (!CollectionUtils.isEmpty(filters)) {
|
|
|
+ // 筛选类目
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_KINDID))) {
|
|
|
+ filter(filters.get(FilterField.GOODS_KINDID), SearchConstants.GOODS_KI_ID_FIELD, booleanQuery);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 筛选品牌
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_BRANDID))) {
|
|
|
+ filter(filters.get(FilterField.GOODS_BRANDID), SearchConstants.GOODS_BR_ID_FIELD, booleanQuery);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 筛选货源
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_STORE_TYPE))) {
|
|
|
+ filter(filters.get(FilterField.GOODS_STORE_TYPE), SearchConstants.GOODS_ST_TYPE_FIELD, booleanQuery);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 筛选货币
|
|
|
+ if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_CRNAME))) {
|
|
|
+ filter(filters.get(FilterField.GOODS_CRNAME), SearchConstants.GOODS_CRNAME_FIELD, booleanQuery);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 价格筛选
|
|
|
+ Object minPriceRmb = filters.get(FilterField.GOODS_MINPRICERMB);
|
|
|
+ Object maxPriceRmb = filters.get(FilterField.GOODS_MAXPRICERMB);
|
|
|
+ Object minPriceUsd = filters.get(FilterField.GOODS_MINPRICEUSD);
|
|
|
+ Object maxPriceUsd = filters.get(FilterField.GOODS_MAXPRICEUSD);
|
|
|
+ // 筛选人民币价格
|
|
|
+ if (!StringUtils.isEmpty(minPriceRmb) || !StringUtils.isEmpty(maxPriceRmb)) {
|
|
|
+ Double minPrice = null;
|
|
|
+ Double maxPrice = null;
|
|
|
+ if (!StringUtils.isEmpty(minPriceRmb)) {
|
|
|
+ minPrice = Double.valueOf(minPriceRmb.toString());
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(maxPriceRmb)) {
|
|
|
+ maxPrice = Double.valueOf(maxPriceRmb.toString());
|
|
|
+ }
|
|
|
+ booleanQuery.add(NumericRangeQuery.newDoubleRange(SearchConstants.GOODS_GO_MINPRICERMB_FIELD,
|
|
|
+ minPrice, maxPrice, true, true), BooleanClause.Occur.FILTER);
|
|
|
+ }
|
|
|
+ // 筛选美元价格
|
|
|
+ if (!StringUtils.isEmpty(minPriceUsd) || !StringUtils.isEmpty(maxPriceUsd)) {
|
|
|
+ Double minPrice = null;
|
|
|
+ Double maxPrice = null;
|
|
|
+ if (!StringUtils.isEmpty(minPriceUsd)) {
|
|
|
+ minPrice = Double.valueOf(minPriceUsd.toString());
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(maxPriceUsd)) {
|
|
|
+ maxPrice = Double.valueOf(maxPriceUsd.toString());
|
|
|
+ }
|
|
|
+ booleanQuery.add(NumericRangeQuery.newDoubleRange(SearchConstants.GOODS_GO_MINPRICEUSD_FIELD,
|
|
|
+ minPrice, maxPrice, true, true), BooleanClause.Occur.FILTER);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+
|
|
|
+ // 排序
|
|
|
+ // 自定义排序字段 > 分数 > 批次(访问量) > 器件(访问量 > 搜索次数) > 品牌(权重 > 访问量 > 搜索次数) > 类目(访问量 > 搜索次数)
|
|
|
+ List<SortField> sortFieldList = new ArrayList<>();
|
|
|
+ List<com.uas.search.constant.model.Sort> sorts = pageParams.getSort();
|
|
|
+ if (sorts != null && !CollectionUtils.isEmpty(sorts)) {
|
|
|
+ SortField[] sortFields = new SortField[sorts.size()];
|
|
|
+ int i = 0;
|
|
|
+ for (com.uas.search.constant.model.Sort s : sorts) {
|
|
|
+ if (s.getField() == null) {
|
|
|
+ throw new SearchException("排序字段不可为空:" + s);
|
|
|
+ }
|
|
|
+ switch (s.getField()) {
|
|
|
+ // 价格
|
|
|
+ case GO_RESERVE:
|
|
|
+ sortFields[i++] = new SortField(SearchConstants.GOODS_GO_RESERVE_FIELD, Type.DOUBLE,
|
|
|
+ s.isReverse());
|
|
|
+ break;
|
|
|
+ // 人民币价格
|
|
|
+ case GO_MINPRICERMB:
|
|
|
+ sortFields[i++] = new SortField(SearchConstants.GOODS_GO_MINPRICERMB_FIELD, Type.DOUBLE,
|
|
|
+ s.isReverse());
|
|
|
+ break;
|
|
|
+ // 美元价格
|
|
|
+ case GO_MINPRICEUSD:
|
|
|
+ sortFields[i++] = new SortField(SearchConstants.GOODS_GO_MINPRICEUSD_FIELD, Type.DOUBLE,
|
|
|
+ s.isReverse());
|
|
|
+ break;
|
|
|
+ // 打分
|
|
|
+ case GO_SEARCH:
|
|
|
+ sortFields[i++] = (SortField.FIELD_SCORE);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new SearchException("不支持该排序方式:" + s.getField());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sortFieldList.addAll(Arrays.asList(sortFields));
|
|
|
+ } else{
|
|
|
+ sortFieldList.add(SortField.FIELD_SCORE);
|
|
|
+ }
|
|
|
+ sortFieldList.addAll(Arrays.asList(sortGoods(keyword)));
|
|
|
+ SortField[] sortFields = new SortField[sortFieldList.size()];
|
|
|
+ sortFieldList.toArray(sortFields);
|
|
|
+ Sort sort = new Sort(sortFields);
|
|
|
+
|
|
|
+ TopDocs hits;
|
|
|
+ if (pageParams.getPage() > 1) {// 不是第一页
|
|
|
+ TopDocs previousHits = indexSearcher.search(booleanQuery,
|
|
|
+ (pageParams.getPage() - 1) * pageParams.getSize(), sort, true, false);
|
|
|
+ int totalHits = previousHits.totalHits;
|
|
|
+ if ((pageParams.getPage() - 1) * pageParams.getSize() >= totalHits) {
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+ ScoreDoc[] previousScoreDocs = previousHits.scoreDocs;
|
|
|
+ ScoreDoc after = previousScoreDocs[previousScoreDocs.length - 1];
|
|
|
+ hits = indexSearcher.searchAfter(after, booleanQuery, pageParams.getSize(), sort, true, false);
|
|
|
+ } else {
|
|
|
+ hits = indexSearcher.search(booleanQuery, pageParams.getSize(), sort, true, false);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果没有结果,则降低精度,直至 keyword 长度为 1
|
|
|
+ if(recursivelyGet && hits.totalHits < 1 && !SearchUtils.isKeywordInvalid(keyword) && keyword.length() > 1){
|
|
|
+ return getGoodsIds(keyword.substring(0, keyword.length() - 1), keywordFields, tokenized, pageParams, recursivelyGet);
|
|
|
+ }
|
|
|
+ // 数据量太大,需要指定将获取的数据(以免载入不必要的数据,降低速度)
|
|
|
+ Set<String> fieldsToLoad = new HashSet<>();
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_CMP_ID_FIELD);
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_GO_ID_FIELD);
|
|
|
+ ScoreDoc[] scoreDocs = hits.scoreDocs;
|
|
|
+ for (ScoreDoc scoreDoc : scoreDocs) {
|
|
|
+ Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
|
|
|
+ String cmpId = document.get(SearchConstants.GOODS_CMP_ID_FIELD);
|
|
|
+ cmpIds.add(StringUtils.isEmpty(cmpId) ? null : Long.valueOf(cmpId));
|
|
|
+ String goId = document.get(SearchConstants.GOODS_GO_ID_FIELD);
|
|
|
+ goIds.add(StringUtils.isEmpty(goId) ? null : Long.valueOf(goId));
|
|
|
+ // System.out.println(cmpId + "\t" + goId + "\t" +
|
|
|
+ // scoreDoc.score);
|
|
|
+ // System.out.println(indexSearcher.explain(booleanQuery,
|
|
|
+ // scoreDoc.doc).toString());
|
|
|
+ }
|
|
|
+ map.put("componentIds", cmpIds);
|
|
|
+ map.put("goodsIds", goIds);
|
|
|
+ map.put("page", pageParams.getPage());
|
|
|
+ map.put("size", pageParams.getSize());
|
|
|
+ map.put("total", hits.totalHits);
|
|
|
+ } catch (IOException e) {
|
|
|
+ logger.error("", e);
|
|
|
+ } finally {
|
|
|
+ SearchUtils.releaseIndexSearcher(indexSearcher);
|
|
|
+ }
|
|
|
+ return map;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return 批次排序规则
|
|
|
+ */
|
|
|
+ private SortField[] sortGoods(String keyword) {
|
|
|
+ // 器件、非标型号自定义排序 > 批次(访问量) > 器件(自定义排序 > 访问量 > 搜索次数) > 品牌(自定义排序 > 权重 > 访问量 > 搜索次数) > 类目(访问量 > 搜索次数)
|
|
|
+ return new SortField[]{
|
|
|
+ new SortField(SearchConstants.GOODS_CMP_CODE_FIELD, new StringFieldComparatorSource(keyword)),
|
|
|
+ new SortField(SearchConstants.GOODS_PR_PCMPCODE_FIELD, new StringFieldComparatorSource(keyword)),
|
|
|
+ sortField(SearchConstants.GOODS_GO_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.GOODS_CMP_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.GOODS_CMP_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ new SortField(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword)),
|
|
|
+ new SortField(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword)),
|
|
|
+ sortField(SearchConstants.GOODS_BR_WEIGHT_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.GOODS_BR_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.GOODS_BR_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.GOODS_KI_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
|
|
|
+ sortField(SearchConstants.GOODS_KI_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE)
|
|
|
+ };
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public List<Map<String, Object>> collectBySearchGoods(String keyword, CollectField collectedField,
|
|
|
+ Map<FilterField, Object> filters) {
|
|
|
+ List<String> keywordFields = new ArrayList<>();
|
|
|
+ // 先根据品牌搜索,品牌不存在再搜索型号等
|
|
|
+ keywordFields.add(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD);
|
|
|
+ keywordFields.add(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD);
|
|
|
+ List<Map<String, Object>> result = collectBySearchGoods(keyword, keywordFields, false, collectedField, filters, false);
|
|
|
+ if (CollectionUtils.isEmpty(result)) {
|
|
|
+ result = collectBySearchGoods(keyword, null, true, collectedField, filters, true);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @param keyword
|
|
|
+ * @param keywordFields
|
|
|
+ * 要查询的字段
|
|
|
+ * @param tokenized
|
|
|
+ * 是否分词
|
|
|
+ * @param collectedField
|
|
|
+ * @param filters
|
|
|
+ * @param recursivelyGet 是否递归获取(逐步降低精度,直到只匹配一个字符)
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private List<Map<String, Object>> collectBySearchGoods(String keyword, List<String> keywordFields,
|
|
|
+ Boolean tokenized, CollectField collectedField, Map<FilterField, Object> filters, Boolean recursivelyGet) {
|
|
|
+ if (collectedField == null && CollectionUtils.isEmpty(filters)) {
|
|
|
+ throw new SearchException("参数不合法:collectedField=" + collectedField + ", filter=" + filters);
|
|
|
+ }
|
|
|
+ // 与批次搜索的搜索词保持一致,最终有结果的 keyword 是相同的
|
|
|
+ if(recursivelyGet != null && recursivelyGet){
|
|
|
+ PageParams pageParams = new PageParams();
|
|
|
+ pageParams.setFilters(filters);
|
|
|
+ Map<String, Object> goodsIds = getGoodsIds(keyword, keywordFields, tokenized, pageParams, false);
|
|
|
+ if(Integer.parseInt(goodsIds.get("total").toString()) < 1 && !SearchUtils.isKeywordInvalid(keyword) && keyword.length() > 1){
|
|
|
+ return collectBySearchGoods(keyword.substring(0, keyword.length() - 1), keywordFields, tokenized, collectedField, filters, true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.GOODS_TABLE_NAME);
|
|
|
+
|
|
|
+ List<Map<String, Object>> result = new ArrayList<>();
|
|
|
+ try {
|
|
|
+ BooleanQuery booleanQuery = queryGoods(keyword, keywordFields, tokenized);
|
|
|
+ // 筛选状态
|
|
|
+ if (!CollectionUtils.isEmpty(filters) && !StringUtils.isEmpty(filters.get(FilterField.GOODS_STATUS))) {
|
|
|
+ filter(filters.get(FilterField.GOODS_STATUS), SearchConstants.GOODS_GO_STATUS_FIELD, booleanQuery);
|
|
|
+ } else {
|
|
|
+ // 未指定时,过滤默认状态
|
|
|
+ filter(Arrays.asList(TradeGoods.VALID_STATUS), SearchConstants.GOODS_GO_STATUS_FIELD, booleanQuery);
|
|
|
+ }
|
|
|
+ // 过滤
|
|
|
+ Set<Entry<FilterField, Object>> entrySet = filters.entrySet();
|
|
|
+ for (Entry<FilterField, Object> entry : entrySet) {
|
|
|
+ switch (entry.getKey()) {
|
|
|
+ case GOODS_KINDID:
|
|
|
+ filter(entry.getValue(), SearchConstants.GOODS_KI_ID_FIELD, booleanQuery);
|
|
|
+ break;
|
|
|
+ case GOODS_BRANDID:
|
|
|
+ filter(entry.getValue(), SearchConstants.GOODS_BR_ID_FIELD, booleanQuery);
|
|
|
+ break;
|
|
|
+ case GOODS_STORE_TYPE:
|
|
|
+ filter(entry.getValue(), SearchConstants.GOODS_ST_TYPE_FIELD, booleanQuery);
|
|
|
+ break;
|
|
|
+ case GOODS_CRNAME:
|
|
|
+ filter(entry.getValue(), SearchConstants.GOODS_CRNAME_FIELD, booleanQuery);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new SearchException("不支持该过滤字段:" + entry.getKey());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+
|
|
|
+ // 统计
|
|
|
+ String uniqueField;
|
|
|
+ Set<String> fieldsToLoad = new HashSet<>();
|
|
|
+ switch (collectedField) {
|
|
|
+ case GOODS_KIND:
|
|
|
+ uniqueField = SearchConstants.GOODS_KI_ID_FIELD;
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_KI_ID_FIELD);
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_KI_NAME_CN_FIELD);
|
|
|
+ break;
|
|
|
+ case GOODS_BRAND:
|
|
|
+ uniqueField = SearchConstants.GOODS_BR_ID_FIELD;
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_BR_ID_FIELD);
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_BR_UUID_FIELD);
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_BR_NAME_CN_FIELD);
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_BR_NAME_EN_FIELD);
|
|
|
+ break;
|
|
|
+ case GOODS_STORE_TYPE:
|
|
|
+ uniqueField = SearchConstants.GOODS_ST_TYPE_FIELD;
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_ST_TYPE_FIELD);
|
|
|
+ break;
|
|
|
+ case GOODS_CRNAME:
|
|
|
+ uniqueField = SearchConstants.GOODS_CRNAME_FIELD;
|
|
|
+ fieldsToLoad.add(SearchConstants.GOODS_CRNAME_FIELD);
|
|
|
+ break;
|
|
|
+ default:
|
|
|
+ throw new SearchException("不支持该统计字段:" + collectedField);
|
|
|
+ }
|
|
|
+ GoodsGroupCollector collector = new GoodsGroupCollector(uniqueField, fieldsToLoad);
|
|
|
+ indexSearcher.search(booleanQuery, collector);
|
|
|
+ result = collector.getValues();
|
|
|
+ } catch (IOException e) {
|
|
|
+ logger.error("", e);
|
|
|
+ } finally {
|
|
|
+ SearchUtils.releaseIndexSearcher(indexSearcher);
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取查询批次的query
|
|
|
+ *
|
|
|
+ * @param keyword
|
|
|
+ * @param keywordFields
|
|
|
+ * @param tokenized
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private BooleanQuery queryGoods(String keyword, List<String> keywordFields, Boolean tokenized) {
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ if (!SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
+ // 未指定搜索的字段,则采用默认搜索逻辑
|
|
|
+ if (CollectionUtils.isEmpty(keywordFields)) {
|
|
|
+ booleanQuery.add(setGoodsBoost(keyword), BooleanClause.Occur.MUST);
|
|
|
+ } else {
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ for (String keywordField : keywordFields) {
|
|
|
+ // 是否分词
|
|
|
+ if (tokenized == null || !tokenized.booleanValue()) {
|
|
|
+ booleanQuery2.add(new TermQuery(new Term(keywordField, keyword.toLowerCase())), BooleanClause.Occur.SHOULD);
|
|
|
+ } else {
|
|
|
+ booleanQuery2.add(SearchUtils.getBooleanQuery(keywordField, keyword), BooleanClause.Occur.SHOULD);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return booleanQuery;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 同时搜索器件、类目、品牌等,并设置boost
|
|
|
+ */
|
|
|
+ private Query setGoodsBoost(String keyword) {
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ // 原厂型号
|
|
|
+ booleanQuery.add(createQuery(SearchConstants.GOODS_CMP_CODE_FIELD, keyword, true, 100), BooleanClause.Occur.SHOULD);
|
|
|
+ // 非标
|
|
|
+ booleanQuery.add(createQuery(SearchConstants.GOODS_PR_PCMPCODE_FIELD, keyword, true, 100), Occur.SHOULD);
|
|
|
+ // 品牌
|
|
|
+ booleanQuery.add(createQuery(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD, keyword, true, 10), BooleanClause.Occur.SHOULD);
|
|
|
+ booleanQuery.add(createQuery(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD, keyword, true, 10), BooleanClause.Occur.SHOULD);
|
|
|
+ // 类目
|
|
|
+ booleanQuery.add(createQuery(SearchConstants.GOODS_KI_NAME_CN_FIELD, keyword, 1), BooleanClause.Occur.SHOULD);
|
|
|
+ // 属性值
|
|
|
+ booleanQuery.add(createQuery(SearchConstants.GOODS_CMP_DESCRIPTION_FIELD, keyword, 1), BooleanClause.Occur.SHOULD);
|
|
|
+ return booleanQuery;
|
|
|
+ }
|
|
|
+
|
|
|
+ private BooleanQuery createQuery(String field, String keyword, float boost){
|
|
|
+ return createQuery(field, keyword, false, boost);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ private BooleanQuery createQuery(String field, String keyword, boolean useRegexpQuery, float boost){
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ if (StringUtils.isEmpty(field) || StringUtils.isEmpty(keyword)) {
|
|
|
+ return booleanQuery;
|
|
|
+ }
|
|
|
+ // 根据空格分隔关键词,分隔的词取或的关系
|
|
|
+ String[] array = keyword.split(" ");
|
|
|
+ for(String str : array){
|
|
|
+ if(!StringUtils.isEmpty(str)){
|
|
|
+ booleanQuery.add(SearchUtils.getBooleanQuery(field, str, useRegexpQuery), Occur.SHOULD);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ booleanQuery.setBoost(boost);
|
|
|
+ return booleanQuery;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 过滤
|
|
|
+ *
|
|
|
+ * @param list
|
|
|
+ * 过滤值列表
|
|
|
+ * @param field
|
|
|
+ * 过滤的字段
|
|
|
+ * @param booleanQuery
|
|
|
+ * 查询条件
|
|
|
+ */
|
|
|
+ @SuppressWarnings("unchecked")
|
|
|
+ private void filter(Object list, String field, BooleanQuery booleanQuery) {
|
|
|
+ List<Object> values;
|
|
|
+ if (list instanceof List) {
|
|
|
+ values = (List<Object>) list;
|
|
|
+ }else{
|
|
|
+ values = new ArrayList<>();
|
|
|
+ values.add(list);
|
|
|
+ }
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ for (Object value : values) {
|
|
|
+ TermQuery query = new TermQuery(new Term(field, value.toString().toLowerCase()));
|
|
|
+ booleanQuery2.add(query, BooleanClause.Occur.SHOULD);
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, BooleanClause.Occur.FILTER);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Kind getKind(Long id) {
|
|
|
+ return DocumentToObjectUtils.toKind(
|
|
|
+ SearchUtils.getDocumentById(SearchConstants.KIND_TABLE_NAME, SearchConstants.KIND_ID_FIELD, id));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Brand getBrand(Long id) {
|
|
|
+ return DocumentToObjectUtils.toBrand(
|
|
|
+ SearchUtils.getDocumentById(SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_ID_FIELD, id));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Component getComponent(Long id) {
|
|
|
+ return DocumentToObjectUtils.toComponent(SearchUtils.getDocumentById(SearchConstants.COMPONENT_TABLE_NAME,
|
|
|
+ SearchConstants.COMPONENT_ID_FIELD, id));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public Goods getGoods(String id) {
|
|
|
+ return DocumentToObjectUtils.toGoods(
|
|
|
+ SearchUtils.getDocumentById(SearchConstants.GOODS_TABLE_NAME, SearchConstants.GOODS_GO_ID_FIELD, id));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SPage<Object> getObjects(String tableName, String keyword, String field, Boolean tokenized, @NotEmpty("page") Integer page, @NotEmpty("size") Integer size) {
|
|
|
+ if (keyword == null) {
|
|
|
+ keyword = "";
|
|
|
+ }
|
|
|
+ if (field == null) {
|
|
|
+ field = SearchUtils.getIdField(tableName);
|
|
|
+ }
|
|
|
+ if (tokenized == null) {
|
|
|
+ tokenized = false;
|
|
|
+ }
|
|
|
+ IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(tableName);
|
|
|
+ SPage<Object> sPage = new SPage<>();
|
|
|
+ try {
|
|
|
+ Query query;
|
|
|
+ if (tokenized) {
|
|
|
+ query = SearchUtils.getBooleanQuery(field, keyword);
|
|
|
+ } else {
|
|
|
+ query = SearchUtils.getRegexpQuery(field, keyword);
|
|
|
+ }
|
|
|
+ // 分页信息
|
|
|
+ if (page > 0) {
|
|
|
+ sPage.setPage(page);
|
|
|
+ } else {
|
|
|
+ sPage.setPage(1);
|
|
|
+ sPage.setFirst(true);
|
|
|
+ }
|
|
|
+ if (size > 0) {
|
|
|
+ sPage.setSize(size);
|
|
|
+ } else {
|
|
|
+ sPage.setSize(20);
|
|
|
+ }
|
|
|
+
|
|
|
+ TopDocs topDocs;
|
|
|
+ // 如果页码不为1
|
|
|
+ if (sPage.getPage() > 1) {
|
|
|
+ TopDocs previousTopDocs = indexSearcher.search(query, (sPage.getPage() - 1) * sPage.getSize());
|
|
|
+ int totalHits = previousTopDocs.totalHits;
|
|
|
+ ScoreDoc[] previousScoreDocs = previousTopDocs.scoreDocs;
|
|
|
+ if ((sPage.getPage() - 1) * sPage.getSize() >= totalHits) {
|
|
|
+ throw new SearchException("页码过大:元素总数量为" + totalHits);
|
|
|
+ }
|
|
|
+ topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], query,
|
|
|
+ sPage.getSize());
|
|
|
+ } else {
|
|
|
+ sPage.setFirst(true);
|
|
|
+ topDocs = indexSearcher.search(query, sPage.getSize());
|
|
|
+ }
|
|
|
+
|
|
|
+ int totalHits = topDocs.totalHits;
|
|
|
+ // 设置总元素个数、页数等信息
|
|
|
+ sPage.setTotalElement(totalHits);
|
|
|
+ int totalPage = (int) Math.ceil(totalHits / (1.0 * sPage.getSize()));
|
|
|
+ sPage.setTotalPage(totalPage);
|
|
|
+ if (totalPage == sPage.getPage()) {
|
|
|
+ sPage.setLast(true);
|
|
|
+ }
|
|
|
+ List<Object> content = new ArrayList<>();
|
|
|
+ for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
|
|
|
+ Document document = indexSearcher.doc(scoreDoc.doc);
|
|
|
+ content.add(DocumentToObjectUtils.toObject(document, tableName));
|
|
|
+ }
|
|
|
+ sPage.setContent(content);
|
|
|
+ } catch (IOException e) {
|
|
|
+ throw new SearchException(e).setDetailedMessage(e);
|
|
|
+ } finally {
|
|
|
+ SearchUtils.releaseIndexSearcher(indexSearcher);
|
|
|
+ }
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+
|
|
|
+}
|