SearchServiceImpl.java 54 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338
  1. package com.uas.search.service.impl;
  2. import com.alibaba.fastjson.JSONObject;
  3. import com.uas.search.annotation.NotEmpty;
  4. import com.uas.search.constant.SearchConstants;
  5. import com.uas.search.constant.model.CollectField;
  6. import com.uas.search.constant.model.PageParams;
  7. import com.uas.search.constant.model.PageParams.FilterField;
  8. import com.uas.search.constant.model.SPage;
  9. import com.uas.search.exception.SearchException;
  10. import com.uas.search.grouping.DistinctGroupCollector;
  11. import com.uas.search.grouping.GoodsGroupCollector;
  12. import com.uas.search.model.*;
  13. import com.uas.search.service.SearchService;
  14. import com.uas.search.sort.StringFieldComparatorSource;
  15. import com.uas.search.util.CollectionUtils;
  16. import com.uas.search.util.DocumentToObjectUtils;
  17. import com.uas.search.util.SearchUtils;
  18. import com.uas.search.util.StringUtils;
  19. import org.apache.lucene.document.Document;
  20. import org.apache.lucene.index.Term;
  21. import org.apache.lucene.search.*;
  22. import org.apache.lucene.search.BooleanClause.Occur;
  23. import org.apache.lucene.search.SortField.Type;
  24. import org.slf4j.Logger;
  25. import org.slf4j.LoggerFactory;
  26. import org.springframework.stereotype.Service;
  27. import java.io.IOException;
  28. import java.net.URLDecoder;
  29. import java.util.*;
  30. import java.util.Map.Entry;
  31. /**
  32. * 搜索索引
  33. *
  34. * @author sunyj
  35. * @since 2016年8月5日 下午2:21:26
  36. */
  37. @Service
  38. public class SearchServiceImpl implements SearchService {
  39. /**
  40. * 获取联想词时返回的最大数目
  41. */
  42. private static final int SIMILAR_NUM = 20;
  43. private static Logger logger = LoggerFactory.getLogger(SearchServiceImpl.class);
  44. @Override
  45. public SPage<Long> getKindIds(String keyword, Integer page, Integer size) {
  46. List<Long> ids = new ArrayList<>();
  47. SPage<Document> documents = getKindDocuments(keyword, page, size);;
  48. SPage<Long> sPage = new SPage<Long>(documents.getTotalPage(), documents.getTotalElement(), documents.getPage(),
  49. documents.getSize(), documents.isFirst(), documents.isLast());
  50. for (Document document : documents.getContent()) {
  51. ids.add(Long.parseLong(document.get(SearchConstants.KIND_ID_FIELD)));
  52. }
  53. sPage.setContent(ids);
  54. return sPage;
  55. }
  56. @Override
  57. public SPage<Map<String, Object>> getKinds(String keyword, Integer page, Integer size) {
  58. List<Map<String, Object>> kinds = new ArrayList<>();
  59. SPage<Document> documents = getKindDocuments(keyword, page, size);
  60. SPage<Map<String, Object>> sPage = new SPage<>(documents.getTotalPage(),
  61. documents.getTotalElement(), documents.getPage(), documents.getSize(), documents.isFirst(),
  62. documents.isLast());
  63. for (Document document : documents.getContent()) {
  64. Map<String, Object> kind = new HashMap<>();
  65. kind.put("id", Long.parseLong(document.get(SearchConstants.KIND_ID_FIELD)));
  66. kind.put("nameCn", document.get(SearchConstants.KIND_NAMECN_FIELD));
  67. kinds.add(kind);
  68. }
  69. sPage.setContent(kinds);
  70. return sPage;
  71. }
  72. private SPage<Document> getKindDocuments(String keyword, Integer page, Integer size){
  73. if (SearchUtils.isKeywordInvalid(keyword)) {
  74. throw new SearchException("搜索关键词无效:" + keyword);
  75. }
  76. BooleanQuery booleanQuery = SearchUtils.getBooleanQuery(SearchConstants.KIND_NAMECN_FIELD, keyword);
  77. logger.info(booleanQuery.toString());
  78. return SearchUtils.getDocuments(SearchConstants.KIND_TABLE_NAME, booleanQuery, new Sort(sortKind(keyword)), page, size);
  79. }
  80. /**
  81. * @return 类目排序规则
  82. */
  83. private SortField[] sortKind(String keyword) {
  84. // 分数 > 访问量 > 搜索次数
  85. return new SortField[]{
  86. sortField(SearchConstants.KIND_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  87. sortField(SearchConstants.KIND_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  88. new SortField(SearchConstants.KIND_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword))
  89. };
  90. }
  91. /**
  92. * 构造 SortField
  93. *
  94. * @param field Name of field to sort by. Can be <code>null</code> if
  95. * <code>type</code> is SCORE or DOC.
  96. * @param type Type of values in the terms.
  97. * @param reverse True if natural order should be reversed.
  98. * @param missingValue Used for 'sortMissingFirst/Last'
  99. * @return SortField
  100. */
  101. private SortField sortField(String field, Type type, boolean reverse, Object missingValue) {
  102. SortField sortField = new SortField(field, type, reverse);
  103. sortField.setMissingValue(missingValue);
  104. return sortField;
  105. }
  106. @Override
  107. public SPage<Long> getBrandIds(String keyword, Integer page, Integer size) {
  108. List<Long> ids = new ArrayList<>();
  109. SPage<Document> documents = getBrandDocuments(keyword, page, size);
  110. SPage<Long> sPage = new SPage<Long>(documents.getTotalPage(), documents.getTotalElement(), documents.getPage(),
  111. documents.getSize(), documents.isFirst(), documents.isLast());
  112. for (Document document : documents.getContent()) {
  113. ids.add(Long.parseLong(document.get(SearchConstants.BRAND_ID_FIELD)));
  114. }
  115. sPage.setContent(ids);
  116. return sPage;
  117. }
  118. @Override
  119. public SPage<Map<String, Object>> getBrands(String keyword, Integer page, Integer size) {
  120. List<Map<String, Object>> brands = new ArrayList<>();
  121. SPage<Document> documents = getBrandDocuments(keyword, page, size);
  122. SPage<Map<String, Object>> sPage = new SPage<Map<String, Object>>(documents.getTotalPage(),
  123. documents.getTotalElement(), documents.getPage(), documents.getSize(), documents.isFirst(),
  124. documents.isLast());
  125. for (Document document : documents.getContent()) {
  126. Map<String, Object> brand = new HashMap<String, Object>();
  127. brand.put("id", Long.parseLong(document.get(SearchConstants.BRAND_ID_FIELD)));
  128. brand.put("uuid", document.get(SearchConstants.BRAND_UUID_FIELD));
  129. brand.put("nameCn", document.get(SearchConstants.BRAND_NAMECN_FIELD));
  130. brand.put("nameEn", document.get(SearchConstants.BRAND_NAMEEN_FIELD));
  131. brands.add(brand);
  132. }
  133. sPage.setContent(brands);
  134. return sPage;
  135. }
  136. private SPage<Document> getBrandDocuments(String keyword, Integer page, Integer size){
  137. if (SearchUtils.isKeywordInvalid(keyword)) {
  138. throw new SearchException("搜索关键词无效:" + keyword);
  139. }
  140. BooleanQuery booleanQuery = new BooleanQuery();
  141. booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMECN_FIELD, keyword),
  142. BooleanClause.Occur.SHOULD);
  143. booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMEEN_FIELD, keyword),
  144. BooleanClause.Occur.SHOULD);
  145. logger.info(booleanQuery.toString());
  146. return SearchUtils.getDocuments(SearchConstants.BRAND_TABLE_NAME, booleanQuery, new Sort(sortBrand(keyword)), page,
  147. size);
  148. }
  149. /**
  150. * @return 品牌排序规则
  151. */
  152. private SortField[] sortBrand(String keyword) {
  153. // 自定义排序 > 权重 > 访问量 > 搜索次数 > 分数
  154. // 分数排序放在最后,是因为有的中英文名称相同,分数翻倍,但实际匹配度并不高
  155. return new SortField[]{
  156. sortField(SearchConstants.BRAND_WEIGHT_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
  157. sortField(SearchConstants.BRAND_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  158. sortField(SearchConstants.BRAND_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  159. new SortField(SearchConstants.BRAND_NAMEEN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
  160. new SortField(SearchConstants.BRAND_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword))
  161. };
  162. }
  163. @Override
  164. public Map<String, Object> getComponentIds(String keyword, PageParams pageParams) {
  165. Map<String, Object> searchComponentIds = getComponentIds(keyword, pageParams, null, null);
  166. return searchComponentIds;
  167. // TODO 对品牌、类目甚至拼音混合搜索(待完善)
  168. // int total = (int) searchComponentIds.get("total");
  169. // if (total != 0) {
  170. // return searchComponentIds;
  171. // }
  172. // List<Long> kindIds = getKindIds(keyword, Occur.SHOULD);
  173. // List<Long> brandIds = getBrandIds(keyword, Occur.SHOULD);
  174. // return getComponentIds(null, pageParams, kindIds, brandIds);
  175. }
  176. /**
  177. * 根据关键词搜索产品
  178. *
  179. * @param keyword
  180. * @param pageParams
  181. * @param kindIds
  182. * @param brandIds
  183. * @return
  184. * @throws SearchException
  185. */
  186. private Map<String, Object> getComponentIds(String keyword, PageParams pageParams, List<Long> kindIds,
  187. List<Long> brandIds) throws SearchException {
  188. // 因为器件、属性值的数据量远比类目、品牌大得多,而且器件搜索可能还需进行分页,
  189. // 所以涉及器件、属性值的搜索,大都不能像类目和品牌一样直接利用SearchUtils.getDocuments方法
  190. IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);
  191. if (pageParams == null) {
  192. pageParams = new PageParams();
  193. }
  194. if (pageParams.getPage() <= 0)
  195. pageParams.setPage(1);
  196. if (pageParams.getSize() <= 0)
  197. pageParams.setSize(20);
  198. Map<String, Object> map = new HashMap<String, Object>();
  199. List<Long> ids = new ArrayList<Long>();
  200. try {
  201. BooleanQuery booleanQuery = new BooleanQuery();
  202. if (!SearchUtils.isKeywordInvalid(keyword)) {
  203. booleanQuery.add(setBoost(keyword), BooleanClause.Occur.MUST);
  204. }
  205. Map<FilterField, Object> filters = pageParams.getFilters();
  206. if (!CollectionUtils.isEmpty(filters)) {
  207. // 筛选类目
  208. if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_KINDID))) {
  209. String kindId = filters.get(FilterField.COMPONENT_KINDID).toString();
  210. TermQuery kindQuery = new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, kindId));
  211. booleanQuery.add(kindQuery, BooleanClause.Occur.MUST);
  212. }
  213. // 筛选品牌
  214. if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_BRANDID))) {
  215. String brandId = filters.get(FilterField.COMPONENT_BRANDID).toString();
  216. TermQuery brandQuery = new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, brandId));
  217. booleanQuery.add(brandQuery, BooleanClause.Occur.MUST);
  218. }
  219. // 库存不为0
  220. if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_RESERVE))) {
  221. Boolean isReserveNotEmpty = (Boolean) filters.get(FilterField.COMPONENT_HAS_RESERVE);
  222. if (isReserveNotEmpty) {
  223. booleanQuery.add(NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_RESERVE_FIELD, 0.0,
  224. Double.MAX_VALUE, false, true), BooleanClause.Occur.MUST);
  225. }
  226. }
  227. // 现货、呆滞库存、样品数量不为0,取或的关系
  228. if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_SAMPLE))
  229. || !StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_ORIGINAL))
  230. || !StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_INACTION_STOCK))) {
  231. BooleanQuery booleanQuery2 = new BooleanQuery();
  232. if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_SAMPLE))) {
  233. booleanQuery2.add(NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_SAMPLE_QTY_FIELD,
  234. 0.0, Double.MAX_VALUE, false, true), BooleanClause.Occur.SHOULD);
  235. }
  236. if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_ORIGINAL))) {
  237. booleanQuery2.add(NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_ORIGINAL_QTY_FIELD,
  238. 0.0, Double.MAX_VALUE, false, true), BooleanClause.Occur.SHOULD);
  239. }
  240. if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_HAS_INACTION_STOCK))) {
  241. booleanQuery2.add(
  242. NumericRangeQuery.newDoubleRange(SearchConstants.COMPONENT_INACTION_STOCK_QTY_FIELD,
  243. 0.0, Double.MAX_VALUE, false, true),
  244. BooleanClause.Occur.SHOULD);
  245. }
  246. booleanQuery.add(booleanQuery2, Occur.MUST);
  247. }
  248. // 属性过滤
  249. if (!StringUtils.isEmpty(filters.get(FilterField.COMPONENT_PROPERTIES))) {
  250. JSONObject proJSON = JSONObject
  251. .parseObject(String.valueOf(filters.get(FilterField.COMPONENT_PROPERTIES)));
  252. for (String key : proJSON.keySet()) {
  253. String value = String.valueOf(proJSON.get(key));
  254. if (!StringUtils.isEmpty(value)) {
  255. if (!key.startsWith(SearchConstants.COMPONENT_PROPERTY_PREFIX)) {
  256. key = SearchConstants.COMPONENT_PROPERTY_PREFIX + key;
  257. }
  258. TermQuery propertyQuery = new TermQuery(new Term(key, value));
  259. booleanQuery.add(propertyQuery, BooleanClause.Occur.MUST);
  260. }
  261. }
  262. }
  263. }
  264. if (!CollectionUtils.isEmpty(kindIds)) {
  265. BooleanQuery booleanQuery2 = new BooleanQuery();
  266. for (Long id : kindIds) {
  267. booleanQuery2.add(new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, id.toString())),
  268. Occur.SHOULD);
  269. }
  270. booleanQuery.add(booleanQuery2, Occur.MUST);
  271. }
  272. if (!CollectionUtils.isEmpty(brandIds)) {
  273. BooleanQuery booleanQuery2 = new BooleanQuery();
  274. for (Long id : brandIds) {
  275. booleanQuery2.add(new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, id.toString())),
  276. Occur.SHOULD);
  277. }
  278. booleanQuery.add(booleanQuery2, Occur.MUST);
  279. }
  280. logger.info(booleanQuery.toString());
  281. Sort sort = new Sort(sortComponent(keyword));
  282. TopDocs hits;
  283. if (pageParams.getPage() > 1) {// 不是第一页
  284. TopDocs previousHits = indexSearcher.search(booleanQuery,
  285. (pageParams.getPage() - 1) * pageParams.getSize(), sort, true, false);
  286. ScoreDoc[] previousScoreDocs = previousHits.scoreDocs;
  287. ScoreDoc after = previousScoreDocs[previousScoreDocs.length - 1];
  288. hits = indexSearcher.searchAfter(after, booleanQuery, pageParams.getSize(), sort, true, false);
  289. } else {
  290. hits = indexSearcher.search(booleanQuery, pageParams.getSize(), sort, true, false);
  291. }
  292. ScoreDoc[] scoreDocs = hits.scoreDocs;
  293. for (ScoreDoc scoreDoc : scoreDocs) {
  294. // 数据量太大,需要指定将获取的数据(以免载入不必要的数据,降低速度)
  295. Set<String> fieldsToLoad = new HashSet<>();
  296. fieldsToLoad.add(SearchConstants.COMPONENT_ID_FIELD);
  297. Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
  298. String componentId = document.get(SearchConstants.COMPONENT_ID_FIELD);
  299. ids.add(Long.parseLong(componentId));
  300. // System.out.println(componentId + "\t" + scoreDoc.score);
  301. }
  302. map.put("componentIds", ids);
  303. map.put("page", pageParams.getPage());
  304. map.put("size", pageParams.getSize());
  305. map.put("total", hits.totalHits);
  306. } catch (IOException e) {
  307. logger.error("", e);
  308. } finally {
  309. SearchUtils.releaseIndexSearcher(indexSearcher);
  310. }
  311. return map;
  312. }
  313. /**
  314. * 同时搜索器件、类目、品牌,并设置boost
  315. *
  316. * @param keyword
  317. * @return
  318. */
  319. private Query setBoost(String keyword) {
  320. BooleanQuery booleanQuery = new BooleanQuery();
  321. PrefixQuery prefixQuery = new PrefixQuery(
  322. new Term(SearchConstants.COMPONENT_CODE_FIELD, keyword.toLowerCase()));
  323. prefixQuery.setBoost(100);
  324. booleanQuery.add(prefixQuery, BooleanClause.Occur.SHOULD);
  325. booleanQuery.add(createQuery(SearchConstants.COMPONENT_BR_NAMECN_FIELD, keyword, 10), BooleanClause.Occur.SHOULD);
  326. booleanQuery.add(createQuery(SearchConstants.COMPONENT_BR_NAMEEN_FIELD, keyword, 10), BooleanClause.Occur.SHOULD);
  327. booleanQuery.add(createQuery(SearchConstants.COMPONENT_KI_NAME_FIELD, keyword, 1), BooleanClause.Occur.SHOULD);
  328. return booleanQuery;
  329. }
  330. /**
  331. * @return 器件排序规则
  332. */
  333. private SortField[] sortComponent(String keyword) {
  334. // 分数 > 器件(访问量 > 搜索次数) > 品牌(权重 > 访问量 > 搜索次数) > 类目(访问量 > 搜索次数)
  335. return new SortField[]{
  336. sortField(SearchConstants.COMPONENT_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  337. sortField(SearchConstants.COMPONENT_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  338. sortField(SearchConstants.COMPONENT_BR_WEIGHT_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
  339. sortField(SearchConstants.COMPONENT_BR_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  340. sortField(SearchConstants.COMPONENT_BR_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  341. sortField(SearchConstants.COMPONENT_KI_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  342. sortField(SearchConstants.COMPONENT_KI_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  343. new SortField(SearchConstants.COMPONENT_CODE_FIELD, new StringFieldComparatorSource(keyword, true)),
  344. new SortField(SearchConstants.COMPONENT_BR_NAMEEN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
  345. new SortField(SearchConstants.COMPONENT_BR_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
  346. new SortField(SearchConstants.COMPONENT_KI_NAME_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword))
  347. };
  348. }
  349. @Override
  350. public Set<Long> getKindIdsBySearchComponent(String keyword, String brandId) {
  351. Query filter = null;
  352. // 筛选品牌
  353. if (!StringUtils.isEmpty(brandId)) {
  354. filter = new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, brandId));
  355. }
  356. return collectBySearchComponent(keyword, filter, SearchConstants.COMPONENT_KI_ID_FIELD).getValues();
  357. }
  358. @Override
  359. public Set<Map<String, Object>> getKindsBySearchComponent(String keyword, String brandId) {
  360. Query filter = null;
  361. // 筛选品牌
  362. if (!StringUtils.isEmpty(brandId)) {
  363. filter = new TermQuery(new Term(SearchConstants.COMPONENT_BR_ID_FIELD, brandId));
  364. }
  365. return collectBySearchComponent(keyword, filter, SearchConstants.KIND_ID_FIELD,
  366. new DistinctGroupCollector.CollectField(SearchConstants.KIND_ID_FIELD, "id"),
  367. new DistinctGroupCollector.CollectField(SearchConstants.KIND_NAMECN_FIELD, "nameCn"))
  368. .getCollectValues();
  369. }
  370. @Override
  371. public Set<Long> getBrandIdsBySearchComponent(String keyword, String kindId) {
  372. Query filter = null;
  373. // 筛选类目
  374. if (!StringUtils.isEmpty(kindId)) {
  375. filter = new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, kindId));
  376. }
  377. return collectBySearchComponent(keyword, filter, SearchConstants.COMPONENT_BR_ID_FIELD).getValues();
  378. }
  379. @Override
  380. public Set<Map<String, Object>> getBrandsBySearchComponent(String keyword, String kindId) {
  381. Query filter = null;
  382. // 筛选类目
  383. if (!StringUtils.isEmpty(kindId)) {
  384. filter = new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, kindId));
  385. }
  386. return collectBySearchComponent(keyword, filter, SearchConstants.COMPONENT_BR_ID_FIELD,
  387. new DistinctGroupCollector.CollectField(SearchConstants.COMPONENT_BR_ID_FIELD, "id"),
  388. new DistinctGroupCollector.CollectField(SearchConstants.COMPONENT_BR_UUID_FIELD, "uuid"),
  389. new DistinctGroupCollector.CollectField(SearchConstants.COMPONENT_BR_NAMECN_FIELD, "nameCn"))
  390. .getCollectValues();
  391. }
  392. /**
  393. * 搜索器件时统计指定信息
  394. *
  395. * @param keyword 关键词
  396. * @param filter 过滤条件
  397. * @param groupField 统计的单个字段(多为 id)
  398. * @param collectFields 统计的多个字段(详细信息),可为空
  399. * @return 统计信息
  400. */
  401. private DistinctGroupCollector collectBySearchComponent(String keyword, Query filter, String groupField, DistinctGroupCollector.CollectField... collectFields){
  402. if (SearchUtils.isKeywordInvalid(keyword)) {
  403. throw new SearchException("搜索关键词无效:" + keyword);
  404. }
  405. IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);
  406. try {
  407. BooleanQuery booleanQuery = new BooleanQuery();
  408. keyword = URLDecoder.decode(keyword, "UTF-8");
  409. booleanQuery.add(setBoost(keyword), BooleanClause.Occur.MUST);
  410. if(filter != null){
  411. booleanQuery.add(filter, Occur.FILTER);
  412. }
  413. logger.info(booleanQuery.toString());
  414. DistinctGroupCollector collector = new DistinctGroupCollector(groupField, collectFields);
  415. indexSearcher.search(booleanQuery, collector);
  416. return collector;
  417. } catch (IOException e) {
  418. throw new IllegalStateException("统计失败", e);
  419. } finally {
  420. SearchUtils.releaseIndexSearcher(indexSearcher);
  421. }
  422. }
  423. @Override
  424. public List<String> getSimilarKeywords(String keyword, Integer size) {
  425. size = size == null || size < 1 ? SIMILAR_NUM : size;
  426. List<String> result = new ArrayList<>();
  427. // 相似的器件原厂型号数量足够,直接返回
  428. List<String> componentCodes = getSimilarComponentCodes(keyword, size);
  429. result.addAll(componentCodes);
  430. removeDuplicate(result);
  431. if (result.size() == size) {
  432. return result;
  433. }
  434. // 获取相似类目
  435. List<String> kindNames = getSimilarKindNames(keyword, size);
  436. if (!CollectionUtils.isEmpty(kindNames)) {
  437. result.addAll(kindNames);
  438. removeDuplicate(result);
  439. // 如果总的数量超出SIMILAR_NUM,去除多余的元素
  440. if (result.size() > size) {
  441. removeElements(result, size);
  442. return result;
  443. }
  444. }
  445. // 获取相似品牌
  446. List<String> brandNames = getSimilarBrandNames(keyword, size);
  447. if (!CollectionUtils.isEmpty(brandNames)) {
  448. result.addAll(brandNames);
  449. removeDuplicate(result);
  450. if (result.size() > size) {
  451. removeElements(result, size);
  452. return result;
  453. }
  454. }
  455. return result;
  456. }
  457. @Override
  458. public List<Map<String, Object>> getSimilarComponents(String componentCode, Integer size) {
  459. size = size == null || size < 1 ? SIMILAR_NUM : size;
  460. if (SearchUtils.isKeywordInvalid(componentCode)) {
  461. throw new SearchException("输入无效:" + componentCode);
  462. }
  463. IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);
  464. List<Map<String, Object>> components = new ArrayList<>();
  465. try {
  466. PrefixQuery prefixQuery = new PrefixQuery(
  467. new Term(SearchConstants.COMPONENT_CODE_FIELD, componentCode.toLowerCase()));
  468. logger.info(prefixQuery.toString());
  469. Sort sort = new Sort(new SortField(SearchConstants.COMPONENT_CODE_FIELD, new StringFieldComparatorSource(componentCode)));
  470. TopDocs hits = indexSearcher.search(prefixQuery, size, sort);
  471. ScoreDoc[] scoreDocs = hits.scoreDocs;
  472. for (ScoreDoc scoreDoc : scoreDocs) {
  473. Set<String> fieldsToLoad = new HashSet<>();
  474. fieldsToLoad.add(SearchConstants.COMPONENT_ID_FIELD);
  475. fieldsToLoad.add(SearchConstants.COMPONENT_UUID_FIELD);
  476. fieldsToLoad.add(SearchConstants.COMPONENT_CODE_FIELD);
  477. Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
  478. Map<String, Object> map = new HashMap<>();
  479. map.put("id", Long.parseLong(document.get(SearchConstants.COMPONENT_ID_FIELD)));
  480. map.put("uuid", document.get(SearchConstants.COMPONENT_UUID_FIELD));
  481. map.put("code", document.get(SearchConstants.COMPONENT_CODE_FIELD));
  482. components.add(map);
  483. }
  484. } catch (IOException e) {
  485. logger.error("", e);
  486. } finally {
  487. SearchUtils.releaseIndexSearcher(indexSearcher);
  488. }
  489. return components;
  490. }
  491. @Override
  492. public List<Map<String, Object>> getSimilarBrands(String brandName, Integer size) {
  493. size = size == null || size < 1 ? SIMILAR_NUM : size;
  494. if (SearchUtils.isKeywordInvalid(brandName)) {
  495. throw new SearchException("输入无效:" + brandName);
  496. }
  497. List<Map<String, Object>> brands = new ArrayList<Map<String, Object>>();
  498. // 品牌名称带有空格,并且中英文名并无一定顺序,因此对nameCn、nameEn均要搜索
  499. BooleanQuery booleanQuery = new BooleanQuery();
  500. // 搜索nameCn
  501. booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMECN_FIELD, brandName),
  502. BooleanClause.Occur.SHOULD);
  503. // 搜索nameEn
  504. booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.BRAND_NAMEEN_FIELD, brandName),
  505. BooleanClause.Occur.SHOULD);
  506. logger.info(booleanQuery.toString());
  507. Sort sort = new Sort(new SortField(SearchConstants.BRAND_NAMEEN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(brandName)),
  508. new SortField(SearchConstants.BRAND_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(brandName)));
  509. List<Document> documents = SearchUtils.getDocuments(SearchConstants.BRAND_TABLE_NAME, booleanQuery, sort, null, size)
  510. .getContent();
  511. for (Document document : documents) {
  512. Map<String, Object> brand = new HashMap<>();
  513. brand.put("id", Long.parseLong(document.get(SearchConstants.BRAND_ID_FIELD)));
  514. brand.put("uuid", document.get(SearchConstants.BRAND_UUID_FIELD));
  515. brand.put("nameCn", document.get(SearchConstants.BRAND_NAMECN_FIELD));
  516. brand.put("nameEn", document.get(SearchConstants.BRAND_NAMEEN_FIELD));
  517. brands.add(brand);
  518. }
  519. return brands;
  520. }
  521. @Override
  522. public List<Map<String, Object>> getSimilarKinds(String kindName, Integer size) {
  523. size = size == null || size < 1 ? SIMILAR_NUM : size;
  524. return getSimilarKinds(kindName, null, null, size);
  525. }
  526. @Override
  527. public List<Map<String, Object>> getSimilarLeafKinds(String kindName, Integer size) {
  528. size = size == null || size < 1 ? SIMILAR_NUM : size;
  529. return getSimilarKinds(kindName, (short) 1, null, size);
  530. }
  531. @Override
  532. public List<Map<String, Object>> getSimilarKindsByLevel(String kindName, Short level, Integer size) {
  533. size = size == null || size < 1 ? SIMILAR_NUM : size;
  534. return getSimilarKinds(kindName, null, level, size);
  535. }
  536. /**
  537. * 根据输入的类目名获取联想词
  538. *
  539. * @param kindName
  540. * 类目名
  541. * @param isLeaf
  542. * 是否只获取末级类目
  543. * @param level
  544. * 指定的类目级别
  545. * @param size 指定的联想词数目
  546. * @return
  547. */
  548. private List<Map<String, Object>> getSimilarKinds(String kindName, Short isLeaf, Short level, Integer size) {
  549. size = size == null || size < 1 ? SIMILAR_NUM : size;
  550. if (SearchUtils.isKeywordInvalid(kindName)) {
  551. throw new SearchException("输入无效:" + kindName);
  552. }
  553. List<Map<String, Object>> kinds = new ArrayList<>();
  554. BooleanQuery booleanQuery = new BooleanQuery();
  555. booleanQuery.add(SearchUtils.getBooleanQuery(SearchConstants.KIND_NAMECN_FIELD, kindName),
  556. BooleanClause.Occur.MUST);
  557. if (isLeaf != null && isLeaf == 1) {
  558. booleanQuery.add(new TermQuery(new Term(SearchConstants.KIND_ISLEAF_FIELD, String.valueOf(isLeaf))),
  559. BooleanClause.Occur.MUST);
  560. } else {
  561. if (level != null && level > 0) {
  562. booleanQuery.add(new TermQuery(new Term(SearchConstants.KIND_LEVEL_FIELD, String.valueOf(level))),
  563. BooleanClause.Occur.MUST);
  564. }
  565. }
  566. logger.info(booleanQuery.toString());
  567. Sort sort = new Sort(new SortField(SearchConstants.KIND_NAMECN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(kindName)));
  568. List<Document> documents = SearchUtils.getDocuments(SearchConstants.KIND_TABLE_NAME, booleanQuery, sort, null, size).getContent();
  569. for (Document document : documents) {
  570. Map<String, Object> map = new HashMap<>();
  571. map.put("id", Long.parseLong(document.get(SearchConstants.KIND_ID_FIELD)));
  572. map.put("nameCn", document.get(SearchConstants.KIND_NAMECN_FIELD));
  573. map.put("isLeaf", Short.parseShort(document.get(SearchConstants.KIND_ISLEAF_FIELD)));
  574. map.put("level", Short.parseShort(document.get(SearchConstants.KIND_LEVEL_FIELD)));
  575. kinds.add(map);
  576. }
  577. return kinds;
  578. }
  579. @Override
  580. public List<Map<String, String>> getSimilarPropertyValues(Long kindId, Long propertyId, String keyword,
  581. Long topNum) {
  582. if (kindId == null || propertyId == null) {
  583. throw new SearchException("类目id和属性id不能为空");
  584. }
  585. IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.COMPONENT_TABLE_NAME);
  586. String propertyIdString = String.valueOf(propertyId);
  587. if (!propertyIdString.startsWith(SearchConstants.COMPONENT_PROPERTY_PREFIX)) {
  588. propertyIdString = SearchConstants.COMPONENT_PROPERTY_PREFIX + propertyIdString;
  589. }
  590. propertyIdString = propertyIdString + SearchConstants.COMPONENT_PROPERTY_TOKENIZED_SUFFIX;
  591. if (keyword == null) {
  592. keyword = "";
  593. }
  594. if (topNum == null || topNum < 1) {
  595. topNum = (long) SIMILAR_NUM;
  596. }
  597. List<String> propertyValues = new ArrayList<>();
  598. try {
  599. BooleanQuery booleanQuery = new BooleanQuery();
  600. booleanQuery.add(new TermQuery(new Term(SearchConstants.COMPONENT_KI_ID_FIELD, String.valueOf(kindId))),
  601. BooleanClause.Occur.MUST);
  602. booleanQuery.add(new PrefixQuery(new Term(propertyIdString, keyword.toLowerCase())),
  603. BooleanClause.Occur.MUST);
  604. logger.info(booleanQuery.toString());
  605. // 如果只搜索topNum个结果,去除重复的属性值后,数目很可能是不够的
  606. TopDocs topDocs = indexSearcher.search(booleanQuery, SearchConstants.TOP_NUM);
  607. ScoreDoc[] scoreDocs = topDocs.scoreDocs;
  608. for (ScoreDoc scoreDoc : scoreDocs) {
  609. Set<String> fieldsToLoad = new HashSet<>();
  610. fieldsToLoad.add(propertyIdString);
  611. Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
  612. String propertyValue = document.get(propertyIdString);
  613. if (!StringUtils.isEmpty(propertyValue) && !propertyValues.contains(propertyValue)) {
  614. propertyValues.add(propertyValue);
  615. }
  616. if (propertyValues.size() >= topNum) {
  617. break;
  618. }
  619. }
  620. } catch (IOException e) {
  621. logger.error("", e);
  622. } finally {
  623. SearchUtils.releaseIndexSearcher(indexSearcher);
  624. }
  625. List<Map<String, String>> result = new ArrayList<>();
  626. for (String propertyValue : propertyValues) {
  627. Map<String, String> map = new HashMap<>();
  628. map.put("propertyValue", propertyValue);
  629. result.add(map);
  630. }
  631. return result;
  632. }
  633. /**
  634. * 根据输入获取相似的器件原厂型号
  635. *
  636. * @param componentCode
  637. * @param size 指定的联想词数目
  638. * @return
  639. */
  640. private List<String> getSimilarComponentCodes(String componentCode, Integer size) {
  641. return getSimilarValues(SearchConstants.COMPONENT_TABLE_NAME, SearchConstants.COMPONENT_CODE_FIELD,
  642. SearchConstants.COMPONENT_CODE_FIELD, componentCode.toLowerCase(), size);
  643. }
  644. /**
  645. * 根据输入获取相似的品牌名称
  646. *
  647. * @param brandName
  648. * @param size 指定的联想词数目
  649. * @return
  650. */
  651. private List<String> getSimilarBrandNames(String brandName, Integer size) {
  652. // 获取相似的中文品牌
  653. List<String> nameCns = getSimilarValues(SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_NAMECN_FIELD,
  654. SearchConstants.BRAND_NAMECN_UNTOKENIZED_FIELD, brandName, size);
  655. // 相似的中文品牌数量足够,直接返回
  656. if (nameCns != null && nameCns.size() == SIMILAR_NUM) {
  657. return nameCns;
  658. }
  659. List<String> names = nameCns;
  660. // 获取相似的英文品牌
  661. List<String> nameEns = getSimilarValues(SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_NAMEEN_FIELD,
  662. SearchConstants.BRAND_NAMEEN_UNTOKENIZED_FIELD, brandName, size);
  663. names.addAll(nameEns);
  664. return names;
  665. }
  666. /**
  667. * 根据输入获取相似的类目名称
  668. *
  669. * @param kindName
  670. * @param size 指定的联想词数目
  671. * @return
  672. */
  673. private List<String> getSimilarKindNames(String kindName, Integer size) {
  674. return getSimilarValues(SearchConstants.KIND_TABLE_NAME, SearchConstants.KIND_NAMECN_FIELD,
  675. SearchConstants.KIND_NAMECN_UNTOKENIZED_FIELD, kindName, size);
  676. }
  677. /**
  678. * 根据输入值获取该域相似的值
  679. *
  680. * @param tableName
  681. * @param field
  682. * @param keyword
  683. * @param size 指定的联想词数目
  684. * @return
  685. */
  686. private List<String> getSimilarValues(String tableName, String field, String sortField, String keyword, Integer size) {
  687. if (SearchUtils.isKeywordInvalid(keyword)) {
  688. throw new SearchException("输入无效:" + keyword);
  689. }
  690. IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(tableName);
  691. List<String> result = new ArrayList<>();
  692. try {
  693. Query query = SearchUtils.getBooleanQuery(field, keyword);
  694. logger.info(query.toString());
  695. Sort sort = new Sort(new SortField(sortField, new StringFieldComparatorSource(keyword)));
  696. TopDocs hits = indexSearcher.search(query, size, sort);
  697. ScoreDoc[] scoreDocs = hits.scoreDocs;
  698. for (ScoreDoc scoreDoc : scoreDocs) {
  699. Set<String> fieldsToLoad = new HashSet<>();
  700. fieldsToLoad.add(field);
  701. Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
  702. result.add(document.get(field));
  703. // System.out.println(document.get(field) + "\t" +
  704. // scoreDoc.score);
  705. }
  706. } catch (IOException e) {
  707. logger.error("", e);
  708. } finally {
  709. SearchUtils.releaseIndexSearcher(indexSearcher);
  710. }
  711. return result;
  712. }
  713. /**
  714. * 移除集合中重复的元素
  715. *
  716. * @param list
  717. * @return
  718. */
  719. private void removeDuplicate(List<String> list) {
  720. if (list == null) {
  721. return;
  722. }
  723. List<String> result = new ArrayList<>();
  724. for (String str : list) {
  725. if (!result.contains(str)) {
  726. result.add(str);
  727. }
  728. }
  729. list.removeAll(list);
  730. list.addAll(result);
  731. }
  732. /**
  733. * 删除集合内 startIndex(含)后的元素
  734. *
  735. * @param list
  736. * @param startIndex
  737. */
  738. private void removeElements(List<? extends String> list, int startIndex) {
  739. if (CollectionUtils.isEmpty(list)) {
  740. return;
  741. }
  742. int listsSize = list.size();
  743. for (int i = listsSize - 1; i >= startIndex; i--) {
  744. list.remove(i);
  745. }
  746. }
  747. @Override
  748. public Map<String, Object> getGoodsIds(String keyword, PageParams pageParams) throws SearchException {
  749. List<String> keywordFields = new ArrayList<>();
  750. // 先根据品牌搜索,品牌不存在再搜索型号等
  751. keywordFields.add(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD);
  752. keywordFields.add(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD);
  753. Map<String, Object> goodsIds = getGoodsIds(keyword, keywordFields, false, pageParams, false);
  754. if (CollectionUtils.isEmpty(goodsIds) || goodsIds.get("componentIds") == null
  755. || JSONObject.parseArray(goodsIds.get("componentIds").toString()).isEmpty()) {
  756. goodsIds = getGoodsIds(keyword, null, true, pageParams, true);
  757. }
  758. return goodsIds;
  759. }
  760. /**
  761. * @param keyword
  762. * @param keywordFields
  763. * 要查询的字段
  764. * @param tokenized
  765. * 是否分词
  766. * @param pageParams
  767. * @param recursivelyGet 是否递归获取(逐步降低精度,直到只匹配一个字符)
  768. * @return
  769. * @throws SearchException
  770. */
  771. private Map<String, Object> getGoodsIds(String keyword, List<String> keywordFields, Boolean tokenized,
  772. PageParams pageParams, Boolean recursivelyGet) throws SearchException {
  773. // 因为器件、属性值的数据量远比类目、品牌大得多,而且器件搜索可能还需进行分页,
  774. // 所以涉及器件、属性值的搜索,大都不能像类目和品牌一样直接利用SearchUtils.getDocuments方法
  775. IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.GOODS_TABLE_NAME);
  776. if (pageParams == null) {
  777. pageParams = new PageParams();
  778. }
  779. if (pageParams.getPage() <= 0)
  780. pageParams.setPage(1);
  781. if (pageParams.getSize() <= 0)
  782. pageParams.setSize(20);
  783. Map<String, Object> map = new HashMap<String, Object>();
  784. List<Long> cmpIds = new ArrayList<>();
  785. List<Long> goIds = new ArrayList<>();
  786. try {
  787. BooleanQuery booleanQuery = queryGoods(keyword, keywordFields, tokenized);
  788. Map<FilterField, Object> filters = pageParams.getFilters();
  789. // 筛选状态
  790. if (!CollectionUtils.isEmpty(filters) && !StringUtils.isEmpty(filters.get(FilterField.GOODS_STATUS))) {
  791. filter(filters.get(FilterField.GOODS_STATUS), SearchConstants.GOODS_GO_STATUS_FIELD, booleanQuery);
  792. } else {
  793. // 未指定时,过滤默认状态
  794. filter(Arrays.asList(TradeGoods.VALID_STATUS), SearchConstants.GOODS_GO_STATUS_FIELD, booleanQuery);
  795. }
  796. if (!CollectionUtils.isEmpty(filters)) {
  797. // 筛选类目
  798. if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_KINDID))) {
  799. filter(filters.get(FilterField.GOODS_KINDID), SearchConstants.GOODS_KI_ID_FIELD, booleanQuery);
  800. }
  801. // 筛选品牌
  802. if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_BRANDID))) {
  803. filter(filters.get(FilterField.GOODS_BRANDID), SearchConstants.GOODS_BR_ID_FIELD, booleanQuery);
  804. }
  805. // 筛选货源
  806. if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_STORE_TYPE))) {
  807. filter(filters.get(FilterField.GOODS_STORE_TYPE), SearchConstants.GOODS_ST_TYPE_FIELD, booleanQuery);
  808. }
  809. // 筛选货币
  810. if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_CRNAME))) {
  811. filter(filters.get(FilterField.GOODS_CRNAME), SearchConstants.GOODS_CRNAME_FIELD, booleanQuery);
  812. }
  813. // 价格筛选
  814. Object minPriceRmb = filters.get(FilterField.GOODS_MINPRICERMB);
  815. Object maxPriceRmb = filters.get(FilterField.GOODS_MAXPRICERMB);
  816. Object minPriceUsd = filters.get(FilterField.GOODS_MINPRICEUSD);
  817. Object maxPriceUsd = filters.get(FilterField.GOODS_MAXPRICEUSD);
  818. // 筛选人民币价格
  819. if (!StringUtils.isEmpty(minPriceRmb) || !StringUtils.isEmpty(maxPriceRmb)) {
  820. Double minPrice = null;
  821. Double maxPrice = null;
  822. if (!StringUtils.isEmpty(minPriceRmb)) {
  823. minPrice = Double.valueOf(minPriceRmb.toString());
  824. }
  825. if (!StringUtils.isEmpty(maxPriceRmb)) {
  826. maxPrice = Double.valueOf(maxPriceRmb.toString());
  827. }
  828. booleanQuery.add(NumericRangeQuery.newDoubleRange(SearchConstants.GOODS_GO_MINPRICERMB_FIELD,
  829. minPrice, maxPrice, true, true), BooleanClause.Occur.FILTER);
  830. }
  831. // 筛选美元价格
  832. if (!StringUtils.isEmpty(minPriceUsd) || !StringUtils.isEmpty(maxPriceUsd)) {
  833. Double minPrice = null;
  834. Double maxPrice = null;
  835. if (!StringUtils.isEmpty(minPriceUsd)) {
  836. minPrice = Double.valueOf(minPriceUsd.toString());
  837. }
  838. if (!StringUtils.isEmpty(maxPriceUsd)) {
  839. maxPrice = Double.valueOf(maxPriceUsd.toString());
  840. }
  841. booleanQuery.add(NumericRangeQuery.newDoubleRange(SearchConstants.GOODS_GO_MINPRICEUSD_FIELD,
  842. minPrice, maxPrice, true, true), BooleanClause.Occur.FILTER);
  843. }
  844. }
  845. logger.info(booleanQuery.toString());
  846. // 排序
  847. // 自定义排序字段 > 批次(访问量) > 器件(访问量 > 搜索次数) > 品牌(权重 > 访问量 > 搜索次数) > 类目(访问量 > 搜索次数) > 分数
  848. SortField[] customSortFields = sortGoods(keyword);
  849. List<SortField> sortFieldList = new ArrayList<>();
  850. List<com.uas.search.constant.model.Sort> sorts = pageParams.getSort();
  851. if (sorts != null && !CollectionUtils.isEmpty(sorts)) {
  852. for (com.uas.search.constant.model.Sort s : sorts) {
  853. if (s.getField() == null) {
  854. throw new SearchException("排序字段不可为空:" + s);
  855. }
  856. switch (s.getField()) {
  857. // 价格
  858. case GO_RESERVE:
  859. sortFieldList.add(new SortField(SearchConstants.GOODS_GO_RESERVE_FIELD, Type.DOUBLE,
  860. s.isReverse()));
  861. break;
  862. // 人民币价格
  863. case GO_MINPRICERMB:
  864. sortFieldList.add(new SortField(SearchConstants.GOODS_GO_MINPRICERMB_FIELD, Type.DOUBLE,
  865. s.isReverse()));
  866. break;
  867. // 美元价格
  868. case GO_MINPRICEUSD:
  869. sortFieldList.add(new SortField(SearchConstants.GOODS_GO_MINPRICEUSD_FIELD, Type.DOUBLE,
  870. s.isReverse()));
  871. break;
  872. // 打分
  873. case GO_SEARCH:
  874. sortFieldList.addAll(Arrays.asList(customSortFields));
  875. break;
  876. default:
  877. throw new SearchException("不支持该排序方式:" + s.getField());
  878. }
  879. }
  880. } else{
  881. sortFieldList.addAll(Arrays.asList(customSortFields));
  882. }
  883. SortField[] sortFields = new SortField[sortFieldList.size()];
  884. sortFieldList.toArray(sortFields);
  885. Sort sort = new Sort(sortFields);
  886. TopDocs hits;
  887. if (pageParams.getPage() > 1) {// 不是第一页
  888. TopDocs previousHits = indexSearcher.search(booleanQuery,
  889. (pageParams.getPage() - 1) * pageParams.getSize(), sort, true, false);
  890. int totalHits = previousHits.totalHits;
  891. if ((pageParams.getPage() - 1) * pageParams.getSize() >= totalHits) {
  892. // 如果没有结果,则降低精度,直至 keyword 长度为 1
  893. if(recursivelyGet && totalHits < 1 && !SearchUtils.isKeywordInvalid(keyword) && keyword.length() > 1){
  894. return getGoodsIds(keyword.substring(0, keyword.length() - 1), keywordFields, tokenized, pageParams, recursivelyGet);
  895. }
  896. return map;
  897. }
  898. ScoreDoc[] previousScoreDocs = previousHits.scoreDocs;
  899. ScoreDoc after = previousScoreDocs[previousScoreDocs.length - 1];
  900. hits = indexSearcher.searchAfter(after, booleanQuery, pageParams.getSize(), sort, true, false);
  901. } else {
  902. hits = indexSearcher.search(booleanQuery, pageParams.getSize(), sort, true, false);
  903. }
  904. // 如果没有结果,则降低精度,直至 keyword 长度为 1
  905. if(recursivelyGet && hits.totalHits < 1 && !SearchUtils.isKeywordInvalid(keyword) && keyword.length() > 1){
  906. return getGoodsIds(keyword.substring(0, keyword.length() - 1), keywordFields, tokenized, pageParams, recursivelyGet);
  907. }
  908. // 数据量太大,需要指定将获取的数据(以免载入不必要的数据,降低速度)
  909. Set<String> fieldsToLoad = new HashSet<>();
  910. fieldsToLoad.add(SearchConstants.GOODS_CMP_ID_FIELD);
  911. fieldsToLoad.add(SearchConstants.GOODS_GO_ID_FIELD);
  912. ScoreDoc[] scoreDocs = hits.scoreDocs;
  913. for (ScoreDoc scoreDoc : scoreDocs) {
  914. Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
  915. String cmpId = document.get(SearchConstants.GOODS_CMP_ID_FIELD);
  916. cmpIds.add(StringUtils.isEmpty(cmpId) ? null : Long.valueOf(cmpId));
  917. String goId = document.get(SearchConstants.GOODS_GO_ID_FIELD);
  918. goIds.add(StringUtils.isEmpty(goId) ? null : Long.valueOf(goId));
  919. // System.out.println(cmpId + "\t" + goId + "\t" +
  920. // scoreDoc.score);
  921. // System.out.println(indexSearcher.explain(booleanQuery,
  922. // scoreDoc.doc).toString());
  923. }
  924. map.put("componentIds", cmpIds);
  925. map.put("goodsIds", goIds);
  926. map.put("page", pageParams.getPage());
  927. map.put("size", pageParams.getSize());
  928. map.put("total", hits.totalHits);
  929. } catch (IOException e) {
  930. logger.error("", e);
  931. } finally {
  932. SearchUtils.releaseIndexSearcher(indexSearcher);
  933. }
  934. return map;
  935. }
  936. /**
  937. * @return 批次排序规则
  938. */
  939. private SortField[] sortGoods(String keyword) {
  940. // 器件、非标型号自定义排序 > 批次(访问量) > 器件(自定义排序 > 访问量 > 搜索次数) > 品牌(自定义排序 > 权重 > 访问量 > 搜索次数) > 类目(访问量 > 搜索次数)
  941. return new SortField[]{
  942. sortField(SearchConstants.GOODS_GO_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  943. sortField(SearchConstants.GOODS_CMP_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  944. sortField(SearchConstants.GOODS_CMP_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  945. sortField(SearchConstants.GOODS_BR_WEIGHT_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
  946. sortField(SearchConstants.GOODS_BR_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  947. sortField(SearchConstants.GOODS_BR_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  948. sortField(SearchConstants.GOODS_KI_VISIT_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  949. sortField(SearchConstants.GOODS_KI_SEARCH_COUNT_FIELD, Type.LONG, true, Long.MIN_VALUE),
  950. new SortField(SearchConstants.GOODS_CMP_CODE_FIELD, new StringFieldComparatorSource(keyword, true)),
  951. new SortField(SearchConstants.GOODS_PR_PCMPCODE_FIELD, new StringFieldComparatorSource(keyword, true)),
  952. new SortField(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
  953. new SortField(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, true)),
  954. new SortField(SearchConstants.GOODS_KI_NAME_CN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword))
  955. };
  956. }
  957. @Override
  958. public List<Map<String, Object>> collectBySearchGoods(String keyword, CollectField collectedField,
  959. Map<FilterField, Object> filters) {
  960. List<String> keywordFields = new ArrayList<>();
  961. // 先根据品牌搜索,品牌不存在再搜索型号等
  962. keywordFields.add(SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD);
  963. keywordFields.add(SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD);
  964. List<Map<String, Object>> result = collectBySearchGoods(keyword, keywordFields, false, collectedField, filters, false);
  965. if (CollectionUtils.isEmpty(result)) {
  966. result = collectBySearchGoods(keyword, null, true, collectedField, filters, true);
  967. }
  968. return result;
  969. }
  970. /**
  971. * @param keyword
  972. * @param keywordFields
  973. * 要查询的字段
  974. * @param tokenized
  975. * 是否分词
  976. * @param collectedField
  977. * @param filters
  978. * @param recursivelyGet 是否递归获取(逐步降低精度,直到只匹配一个字符)
  979. * @return
  980. */
  981. private List<Map<String, Object>> collectBySearchGoods(String keyword, List<String> keywordFields,
  982. Boolean tokenized, CollectField collectedField, Map<FilterField, Object> filters, Boolean recursivelyGet) {
  983. if (collectedField == null && CollectionUtils.isEmpty(filters)) {
  984. throw new SearchException("参数不合法:collectedField=" + collectedField + ", filter=" + filters);
  985. }
  986. // 与批次搜索的搜索词保持一致,最终有结果的 keyword 是相同的
  987. if(recursivelyGet != null && recursivelyGet){
  988. PageParams pageParams = new PageParams();
  989. pageParams.setFilters(filters);
  990. Map<String, Object> goodsIds = getGoodsIds(keyword, keywordFields, tokenized, pageParams, false);
  991. if(Integer.parseInt(goodsIds.get("total").toString()) < 1 && !SearchUtils.isKeywordInvalid(keyword) && keyword.length() > 1){
  992. return collectBySearchGoods(keyword.substring(0, keyword.length() - 1), keywordFields, tokenized, collectedField, filters, true);
  993. }
  994. }
  995. IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.GOODS_TABLE_NAME);
  996. List<Map<String, Object>> result = new ArrayList<>();
  997. try {
  998. BooleanQuery booleanQuery = queryGoods(keyword, keywordFields, tokenized);
  999. // 筛选状态
  1000. if (!CollectionUtils.isEmpty(filters) && !StringUtils.isEmpty(filters.get(FilterField.GOODS_STATUS))) {
  1001. filter(filters.get(FilterField.GOODS_STATUS), SearchConstants.GOODS_GO_STATUS_FIELD, booleanQuery);
  1002. } else {
  1003. // 未指定时,过滤默认状态
  1004. filter(Arrays.asList(TradeGoods.VALID_STATUS), SearchConstants.GOODS_GO_STATUS_FIELD, booleanQuery);
  1005. }
  1006. // 过滤
  1007. Set<Entry<FilterField, Object>> entrySet = filters.entrySet();
  1008. for (Entry<FilterField, Object> entry : entrySet) {
  1009. switch (entry.getKey()) {
  1010. case GOODS_KINDID:
  1011. filter(entry.getValue(), SearchConstants.GOODS_KI_ID_FIELD, booleanQuery);
  1012. break;
  1013. case GOODS_BRANDID:
  1014. filter(entry.getValue(), SearchConstants.GOODS_BR_ID_FIELD, booleanQuery);
  1015. break;
  1016. case GOODS_STORE_TYPE:
  1017. filter(entry.getValue(), SearchConstants.GOODS_ST_TYPE_FIELD, booleanQuery);
  1018. break;
  1019. case GOODS_CRNAME:
  1020. filter(entry.getValue(), SearchConstants.GOODS_CRNAME_FIELD, booleanQuery);
  1021. break;
  1022. default:
  1023. throw new SearchException("不支持该过滤字段:" + entry.getKey());
  1024. }
  1025. }
  1026. logger.info(booleanQuery.toString());
  1027. // 统计
  1028. String uniqueField;
  1029. Set<String> fieldsToLoad = new HashSet<>();
  1030. switch (collectedField) {
  1031. case GOODS_KIND:
  1032. uniqueField = SearchConstants.GOODS_KI_ID_FIELD;
  1033. fieldsToLoad.add(SearchConstants.GOODS_KI_ID_FIELD);
  1034. fieldsToLoad.add(SearchConstants.GOODS_KI_NAME_CN_FIELD);
  1035. break;
  1036. case GOODS_BRAND:
  1037. uniqueField = SearchConstants.GOODS_BR_ID_FIELD;
  1038. fieldsToLoad.add(SearchConstants.GOODS_BR_ID_FIELD);
  1039. fieldsToLoad.add(SearchConstants.GOODS_BR_UUID_FIELD);
  1040. fieldsToLoad.add(SearchConstants.GOODS_BR_NAME_CN_FIELD);
  1041. fieldsToLoad.add(SearchConstants.GOODS_BR_NAME_EN_FIELD);
  1042. break;
  1043. case GOODS_STORE_TYPE:
  1044. uniqueField = SearchConstants.GOODS_ST_TYPE_FIELD;
  1045. fieldsToLoad.add(SearchConstants.GOODS_ST_TYPE_FIELD);
  1046. break;
  1047. case GOODS_CRNAME:
  1048. uniqueField = SearchConstants.GOODS_CRNAME_FIELD;
  1049. fieldsToLoad.add(SearchConstants.GOODS_CRNAME_FIELD);
  1050. break;
  1051. default:
  1052. throw new SearchException("不支持该统计字段:" + collectedField);
  1053. }
  1054. GoodsGroupCollector collector = new GoodsGroupCollector(uniqueField, fieldsToLoad);
  1055. indexSearcher.search(booleanQuery, collector);
  1056. result = collector.getValues();
  1057. } catch (IOException e) {
  1058. logger.error("", e);
  1059. } finally {
  1060. SearchUtils.releaseIndexSearcher(indexSearcher);
  1061. }
  1062. return result;
  1063. }
  1064. /**
  1065. * 获取查询批次的query
  1066. *
  1067. * @param keyword
  1068. * @param keywordFields
  1069. * @param tokenized
  1070. * @return
  1071. */
  1072. private BooleanQuery queryGoods(String keyword, List<String> keywordFields, Boolean tokenized) {
  1073. BooleanQuery booleanQuery = new BooleanQuery();
  1074. if (!SearchUtils.isKeywordInvalid(keyword)) {
  1075. // 未指定搜索的字段,则采用默认搜索逻辑
  1076. if (CollectionUtils.isEmpty(keywordFields)) {
  1077. booleanQuery.add(setGoodsBoost(keyword), BooleanClause.Occur.MUST);
  1078. } else {
  1079. BooleanQuery booleanQuery2 = new BooleanQuery();
  1080. for (String keywordField : keywordFields) {
  1081. // 是否分词
  1082. if (tokenized == null || !tokenized.booleanValue()) {
  1083. booleanQuery2.add(new TermQuery(new Term(keywordField, keyword.toLowerCase())), BooleanClause.Occur.SHOULD);
  1084. } else {
  1085. booleanQuery2.add(SearchUtils.getBooleanQuery(keywordField, keyword), BooleanClause.Occur.SHOULD);
  1086. }
  1087. }
  1088. booleanQuery.add(booleanQuery2, BooleanClause.Occur.MUST);
  1089. }
  1090. }
  1091. return booleanQuery;
  1092. }
  1093. /**
  1094. * 同时搜索器件、类目、品牌等,并设置boost
  1095. */
  1096. private Query setGoodsBoost(String keyword) {
  1097. BooleanQuery booleanQuery = new BooleanQuery();
  1098. // 原厂型号
  1099. booleanQuery.add(createQuery(SearchConstants.GOODS_CMP_CODE_FIELD, keyword, 100), BooleanClause.Occur.SHOULD);
  1100. // 非标
  1101. booleanQuery.add(createQuery(SearchConstants.GOODS_PR_PCMPCODE_FIELD, keyword, 100), Occur.SHOULD);
  1102. // 品牌
  1103. booleanQuery.add(createQuery(SearchConstants.GOODS_BR_NAME_CN_FIELD, keyword, 10), BooleanClause.Occur.SHOULD);
  1104. booleanQuery.add(createQuery(SearchConstants.GOODS_BR_NAME_EN_FIELD, keyword, 10), BooleanClause.Occur.SHOULD);
  1105. // 类目
  1106. booleanQuery.add(createQuery(SearchConstants.GOODS_KI_NAME_CN_FIELD, keyword, 1), BooleanClause.Occur.SHOULD);
  1107. // 属性值
  1108. booleanQuery.add(createQuery(SearchConstants.GOODS_CMP_DESCRIPTION_FIELD, keyword, 1), BooleanClause.Occur.SHOULD);
  1109. return booleanQuery;
  1110. }
  1111. private BooleanQuery createQuery(String field, String keyword, float boost){
  1112. return createQuery(field, keyword, false, boost);
  1113. }
  1114. private BooleanQuery createQuery(String field, String keyword, boolean useRegexpQuery, float boost){
  1115. BooleanQuery booleanQuery = new BooleanQuery();
  1116. if (StringUtils.isEmpty(field) || StringUtils.isEmpty(keyword)) {
  1117. return booleanQuery;
  1118. }
  1119. // 根据空格分隔关键词,分隔的词取或的关系
  1120. String[] array = keyword.split(" ");
  1121. for(String str : array){
  1122. if(!StringUtils.isEmpty(str)){
  1123. booleanQuery.add(SearchUtils.getBooleanQuery(field, str, useRegexpQuery), Occur.SHOULD);
  1124. }
  1125. }
  1126. booleanQuery.setBoost(boost);
  1127. return booleanQuery;
  1128. }
  1129. /**
  1130. * 过滤
  1131. *
  1132. * @param list
  1133. * 过滤值列表
  1134. * @param field
  1135. * 过滤的字段
  1136. * @param booleanQuery
  1137. * 查询条件
  1138. */
  1139. @SuppressWarnings("unchecked")
  1140. private void filter(Object list, String field, BooleanQuery booleanQuery) {
  1141. List<Object> values;
  1142. if (list instanceof List) {
  1143. values = (List<Object>) list;
  1144. }else{
  1145. values = new ArrayList<>();
  1146. values.add(list);
  1147. }
  1148. BooleanQuery booleanQuery2 = new BooleanQuery();
  1149. for (Object value : values) {
  1150. TermQuery query = new TermQuery(new Term(field, value.toString().toLowerCase()));
  1151. booleanQuery2.add(query, BooleanClause.Occur.SHOULD);
  1152. }
  1153. booleanQuery.add(booleanQuery2, BooleanClause.Occur.FILTER);
  1154. }
  1155. @Override
  1156. public Kind getKind(Long id) {
  1157. return DocumentToObjectUtils.toKind(
  1158. SearchUtils.getDocumentById(SearchConstants.KIND_TABLE_NAME, SearchConstants.KIND_ID_FIELD, id));
  1159. }
  1160. @Override
  1161. public Brand getBrand(Long id) {
  1162. return DocumentToObjectUtils.toBrand(
  1163. SearchUtils.getDocumentById(SearchConstants.BRAND_TABLE_NAME, SearchConstants.BRAND_ID_FIELD, id));
  1164. }
  1165. @Override
  1166. public Component getComponent(Long id) {
  1167. return DocumentToObjectUtils.toComponent(SearchUtils.getDocumentById(SearchConstants.COMPONENT_TABLE_NAME,
  1168. SearchConstants.COMPONENT_ID_FIELD, id));
  1169. }
  1170. @Override
  1171. public Goods getGoods(String id) {
  1172. return DocumentToObjectUtils.toGoods(
  1173. SearchUtils.getDocumentById(SearchConstants.GOODS_TABLE_NAME, SearchConstants.GOODS_GO_ID_FIELD, id));
  1174. }
  1175. @Override
  1176. public SPage<Object> getObjects(String tableName, String keyword, String field, Boolean tokenized, @NotEmpty("page") Integer page, @NotEmpty("size") Integer size) {
  1177. if (keyword == null) {
  1178. keyword = "";
  1179. }
  1180. if (field == null) {
  1181. field = SearchUtils.getIdField(tableName);
  1182. }
  1183. if (tokenized == null) {
  1184. tokenized = false;
  1185. }
  1186. IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(tableName);
  1187. SPage<Object> sPage = new SPage<>();
  1188. try {
  1189. Query query;
  1190. if (tokenized) {
  1191. query = SearchUtils.getBooleanQuery(field, keyword);
  1192. } else {
  1193. query = SearchUtils.getRegexpQuery(field, keyword);
  1194. }
  1195. // 分页信息
  1196. if (page > 0) {
  1197. sPage.setPage(page);
  1198. } else {
  1199. sPage.setPage(1);
  1200. sPage.setFirst(true);
  1201. }
  1202. if (size > 0) {
  1203. sPage.setSize(size);
  1204. } else {
  1205. sPage.setSize(20);
  1206. }
  1207. TopDocs topDocs;
  1208. // 如果页码不为1
  1209. if (sPage.getPage() > 1) {
  1210. TopDocs previousTopDocs = indexSearcher.search(query, (sPage.getPage() - 1) * sPage.getSize());
  1211. int totalHits = previousTopDocs.totalHits;
  1212. ScoreDoc[] previousScoreDocs = previousTopDocs.scoreDocs;
  1213. if ((sPage.getPage() - 1) * sPage.getSize() >= totalHits) {
  1214. throw new SearchException("页码过大:元素总数量为" + totalHits);
  1215. }
  1216. topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], query,
  1217. sPage.getSize());
  1218. } else {
  1219. sPage.setFirst(true);
  1220. topDocs = indexSearcher.search(query, sPage.getSize());
  1221. }
  1222. int totalHits = topDocs.totalHits;
  1223. // 设置总元素个数、页数等信息
  1224. sPage.setTotalElement(totalHits);
  1225. int totalPage = (int) Math.ceil(totalHits / (1.0 * sPage.getSize()));
  1226. sPage.setTotalPage(totalPage);
  1227. if (totalPage == sPage.getPage()) {
  1228. sPage.setLast(true);
  1229. }
  1230. List<Object> content = new ArrayList<>();
  1231. for (ScoreDoc scoreDoc : topDocs.scoreDocs) {
  1232. Document document = indexSearcher.doc(scoreDoc.doc);
  1233. content.add(DocumentToObjectUtils.toObject(document, tableName));
  1234. }
  1235. sPage.setContent(content);
  1236. } catch (IOException e) {
  1237. throw new SearchException(e).setDetailedMessage(e);
  1238. } finally {
  1239. SearchUtils.releaseIndexSearcher(indexSearcher);
  1240. }
  1241. return sPage;
  1242. }
  1243. }