SearchServiceImpl.java 47 KB

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