|
@@ -5,13 +5,17 @@ import static com.uas.search.constant.model.Sort.Field.RESERVE;
|
|
|
import static com.uas.search.util.SearchUtils.getDocuments;
|
|
|
import static com.uas.search.util.SearchUtils.releaseIndexSearcher;
|
|
|
|
|
|
+import com.alibaba.fastjson.JSONArray;
|
|
|
import com.alibaba.fastjson.JSONObject;
|
|
|
import com.uas.search.annotation.NotEmpty;
|
|
|
import com.uas.search.constant.SearchConstants;
|
|
|
import com.uas.search.constant.model.CollectField;
|
|
|
+import com.uas.search.constant.model.MultiValue;
|
|
|
import com.uas.search.constant.model.PageParams;
|
|
|
import com.uas.search.constant.model.PageParams.FilterField;
|
|
|
import com.uas.search.constant.model.SPage;
|
|
|
+import com.uas.search.constant.model.SortPlus;
|
|
|
+import com.uas.search.exception.SearchException;
|
|
|
import com.uas.search.grouping.DistinctGroupCollector;
|
|
|
import com.uas.search.grouping.GoodsGroupCollector;
|
|
|
import com.uas.search.model.Brand;
|
|
@@ -23,6 +27,7 @@ import com.uas.search.model.TradeGoods;
|
|
|
import com.uas.search.model.V_Products;
|
|
|
import com.uas.search.service.SearchService;
|
|
|
import com.uas.search.sort.StringFieldComparatorSource;
|
|
|
+import com.uas.search.util.ClassAndTableNameUtils;
|
|
|
import com.uas.search.util.CollectionUtils;
|
|
|
import com.uas.search.util.DocumentToObjectUtils;
|
|
|
import com.uas.search.util.ObjectToDocumentUtils;
|
|
@@ -32,12 +37,15 @@ import java.io.IOException;
|
|
|
import java.net.URLDecoder;
|
|
|
import java.util.ArrayList;
|
|
|
import java.util.Arrays;
|
|
|
+import java.util.Calendar;
|
|
|
import java.util.Collections;
|
|
|
import java.util.Comparator;
|
|
|
+import java.util.Date;
|
|
|
import java.util.HashMap;
|
|
|
import java.util.HashSet;
|
|
|
import java.util.List;
|
|
|
import java.util.Map;
|
|
|
+import java.util.Map.Entry;
|
|
|
import java.util.Set;
|
|
|
import org.apache.lucene.document.Document;
|
|
|
import org.apache.lucene.index.Term;
|
|
@@ -135,7 +143,390 @@ public class SearchServiceImpl implements SearchService {
|
|
|
return sPage;
|
|
|
}
|
|
|
|
|
|
- private SPage<Document> getProductDocuments(Long enUU, String keyword, Integer page, Integer size, String type) throws IOException {
|
|
|
+ /**
|
|
|
+ * 查询所有物料(B2B)
|
|
|
+ *
|
|
|
+ * @return idPage
|
|
|
+ * @throws IOException 输入异常
|
|
|
+ */
|
|
|
+ @Override
|
|
|
+ public SPage<Long> getProductIdsB2B(String keyword, String tableName, Integer page, Integer size, String keywordSearchColumns, String filters, String multiValueField, String sort, String notEqualFilters, String multiValueNotField) throws IOException {
|
|
|
+ Table_name tbName = null;
|
|
|
+ if (!StringUtils.isEmpty(tableName)) {
|
|
|
+ tbName = Table_name.valueOf(tableName.toUpperCase());
|
|
|
+ }
|
|
|
+ PageParams pageParams = new PageParams();
|
|
|
+ if (page != null) {
|
|
|
+ pageParams.setPage(page);
|
|
|
+ }
|
|
|
+ if (size != null) {
|
|
|
+ pageParams.setSize(size);
|
|
|
+ }
|
|
|
+
|
|
|
+ List<String> keywordSearchColumnsList = null;
|
|
|
+ if (!StringUtils.isEmpty(keywordSearchColumns)) {
|
|
|
+ String[] strs = keywordSearchColumns.split(",");
|
|
|
+ keywordSearchColumnsList = new ArrayList<>();
|
|
|
+ for (String str : strs) {
|
|
|
+ keywordSearchColumnsList.add(str);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ pageParams.setKeywordSearchColumns(keywordSearchColumnsList);
|
|
|
+
|
|
|
+ Map<String, Object> filtersMap = new HashMap<>();
|
|
|
+ if (!StringUtils.isEmpty(filters)) {
|
|
|
+ filtersMap.putAll(JSONObject.parseObject(filters));
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(multiValueField)) {
|
|
|
+ JSONArray jsonArray = JSONObject.parseArray(multiValueField);
|
|
|
+ for (int i = 0; i < jsonArray.size(); i++) {
|
|
|
+ JSONObject jsonObject = jsonArray.getJSONObject(i);
|
|
|
+ String field = jsonObject.getString("field");
|
|
|
+ MultiValue multiValue = jsonObject.getObject("multiValue", MultiValue.class);
|
|
|
+ filtersMap.put(field, multiValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(sort)) {
|
|
|
+ filtersMap.put(SearchConstants.SORT_KEY, JSONObject.parseArray(sort, Sort.class));
|
|
|
+ }
|
|
|
+ if (!CollectionUtils.isEmpty(filtersMap)) {
|
|
|
+ pageParams.setCommonFilters(filtersMap);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> notEqualFiltersMap = new HashMap<>();
|
|
|
+ if (!StringUtils.isEmpty(notEqualFilters)) {
|
|
|
+ notEqualFiltersMap.putAll(JSONObject.parseObject(notEqualFilters));
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(multiValueNotField)) {
|
|
|
+ JSONArray jsonArray = JSONObject.parseArray(multiValueNotField);
|
|
|
+ for (int i = 0; i < jsonArray.size(); i++) {
|
|
|
+ JSONObject jsonObject = jsonArray.getJSONObject(i);
|
|
|
+ String field = jsonObject.getString("field");
|
|
|
+ MultiValue multiValue = jsonObject.getObject("multiValue", MultiValue.class);
|
|
|
+ notEqualFiltersMap.put(field, multiValue);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (!CollectionUtils.isEmpty(notEqualFiltersMap)) {
|
|
|
+ pageParams.setNotEqualFilters(notEqualFiltersMap);
|
|
|
+ }
|
|
|
+ return searchIds(keyword, tbName, pageParams);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public SPage<Long> searchIds(String keyword, Table_name tableName, PageParams pageParams) throws SearchException {
|
|
|
+ IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(tableName.value());
|
|
|
+ // 获取单据的id
|
|
|
+ List<Long> content = new ArrayList<>();
|
|
|
+ try {
|
|
|
+ // 获取该表keyword可以搜索的域
|
|
|
+ List<String> keywordFields = null;
|
|
|
+ // 若未指定,再获取实体类中所配置的
|
|
|
+ if (pageParams != null && !CollectionUtils.isEmpty(pageParams.getKeywordSearchColumns())) {
|
|
|
+ keywordFields = getKeywordFields(tableName, pageParams.getKeywordSearchColumns());
|
|
|
+ } else {
|
|
|
+ keywordFields = ClassAndTableNameUtils.getKeywordFields(tableName);
|
|
|
+ }
|
|
|
+ SPage<ScoreDoc> scoreDocPage = search(indexSearcher, keyword, tableName, keywordFields, true, pageParams);
|
|
|
+ SPage<Long> sPage = convertSPage(scoreDocPage, Long.class);
|
|
|
+ for (ScoreDoc scoreDoc : scoreDocPage.getContent()) {
|
|
|
+ Document document = indexSearcher.doc(scoreDoc.doc);
|
|
|
+ content.add(Long.valueOf(document.get(ClassAndTableNameUtils.getIdField(tableName))));
|
|
|
+ }
|
|
|
+ sPage.setContent(content);
|
|
|
+ return sPage;
|
|
|
+ } catch (NumberFormatException | IOException e) {
|
|
|
+ throw new SearchException(e).setDetailedMessage(e);
|
|
|
+ } finally {
|
|
|
+ SearchUtils.releaseIndexSearcher(indexSearcher);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private SPage<ScoreDoc> search(IndexSearcher indexSearcher, String keyword, Table_name tableName,
|
|
|
+ List<String> keywordFields, Boolean tokenized, PageParams pageParams) throws IOException {
|
|
|
+ if (CollectionUtils.isEmpty(keywordFields)) {
|
|
|
+ throw new IllegalArgumentException("keywordFields不可为空");
|
|
|
+ }
|
|
|
+
|
|
|
+ SPage<ScoreDoc> sPage = new SPage<>();
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ Sort sort = null;
|
|
|
+
|
|
|
+ if (keyword == null) {
|
|
|
+ keyword = "";
|
|
|
+ }
|
|
|
+ // 关键词无效,即搜索所有的数据
|
|
|
+ // 关键词带空格,进行与操作
|
|
|
+ String[] strs = keyword.split(" ");
|
|
|
+ for (String str : strs) {
|
|
|
+ // keyword可能是哪些域,域之间进行或操作
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ for (String keywordField : keywordFields) {
|
|
|
+ booleanQuery2.add(SearchUtils.regexpQuery(keywordField, str, tokenized), BooleanClause.Occur.SHOULD);
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+
|
|
|
+ // 分页信息
|
|
|
+ if (pageParams == null) {
|
|
|
+ sPage.setPage(PAGE_INDEX);
|
|
|
+ sPage.setSize(PAGE_SIZE);
|
|
|
+ sPage.setFirst(true);
|
|
|
+ } else {
|
|
|
+ int page = pageParams.getPage();
|
|
|
+ if (page > 0) {
|
|
|
+ sPage.setPage(page);
|
|
|
+ } else {
|
|
|
+ sPage.setPage(PAGE_INDEX);
|
|
|
+ sPage.setFirst(true);
|
|
|
+ }
|
|
|
+ int size = pageParams.getSize();
|
|
|
+ if (size > 0) {
|
|
|
+ sPage.setSize(size);
|
|
|
+ } else {
|
|
|
+ sPage.setSize(PAGE_SIZE);
|
|
|
+ }
|
|
|
+
|
|
|
+ Map<String, Object> filters = pageParams.getCommonFilters();
|
|
|
+ if (!CollectionUtils.isEmpty(filters)) {
|
|
|
+ // 如果filter中开始时间和截止时间至少传了一个参数,说明需要对时间范围进行限定
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.FROM_DATE_KEY))
|
|
|
+ || !StringUtils.isEmpty(filters.get(SearchConstants.END_DATE_KEY))) {
|
|
|
+ // 开始时间初始化为1990.1.1
|
|
|
+ Calendar calendar = Calendar.getInstance();
|
|
|
+ calendar.set(1990, 0, 1);
|
|
|
+ // 截止时间初始化为现在
|
|
|
+ long fromTime = calendar.getTimeInMillis();
|
|
|
+ long endTime = new Date().getTime();
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.FROM_DATE_KEY))) {
|
|
|
+ fromTime = Long.parseLong(
|
|
|
+ filters.remove(SearchConstants.FROM_DATE_KEY).toString());
|
|
|
+ }
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.END_DATE_KEY))) {
|
|
|
+ endTime = Long.parseLong(
|
|
|
+ filters.remove(SearchConstants.END_DATE_KEY).toString());
|
|
|
+ }
|
|
|
+ String dateField = ClassAndTableNameUtils.getDateField(tableName);
|
|
|
+ if (!StringUtils.isEmpty(dateField)) {
|
|
|
+ booleanQuery.add(NumericRangeQuery.newLongRange(dateField, fromTime, endTime, true, true), BooleanClause.Occur.MUST);
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ // 如果需要排序
|
|
|
+ if (!StringUtils.isEmpty(filters.get(SearchConstants.SORT_KEY))) {
|
|
|
+ Object sortObject = filters.remove(SearchConstants.SORT_KEY);
|
|
|
+ List<SortPlus> sortList = null;
|
|
|
+ if (sortObject instanceof ArrayList) {
|
|
|
+ sortList = (List<SortPlus>) sortObject;
|
|
|
+ }
|
|
|
+ boolean sortFieldsIsNull = true;
|
|
|
+ if (!CollectionUtils.isEmpty(sortList)) {
|
|
|
+ SortField[] sortFields = new SortField[sortList.size()];
|
|
|
+ for (int i = 0; i < sortList.size(); i++) {
|
|
|
+ try {
|
|
|
+ SortPlus s = sortList.get(i);
|
|
|
+ SortField sortField = new SortField(s.getField(), getType(s), !s.isReverse());
|
|
|
+ if (s.getMissingValue() != null) {
|
|
|
+ sortField.setMissingValue(s.getMissingValue());
|
|
|
+ }
|
|
|
+ sortFields[i] = sortField;
|
|
|
+ sortFieldsIsNull = true;
|
|
|
+ } catch (ClassCastException e) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ sort = sortFieldsIsNull ? null : new Sort(sortFields);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 其他过滤条件,键即为相应的数据库字段名(field名)
|
|
|
+ Set<Entry<String, Object>> entrySet = filters.entrySet();
|
|
|
+ for (Entry<String, Object> entry : entrySet) {
|
|
|
+ String key = entry.getKey();
|
|
|
+ Object value = entry.getValue();
|
|
|
+ // 拼接索引域名
|
|
|
+ String field = key;
|
|
|
+ /*
|
|
|
+ * 对于明细、企业等所关联的表的数据,是以json格式存储的,搜索时可能会对明细等表中多个字段进行过滤,
|
|
|
+ * 但是键都是明细等表在主表中所对应的字段名称,为了实现这样的过滤功能,需要将多个值放在com.uas.search.
|
|
|
+ * b2b.model.MultiValue中, 作为filters中key-value的value传递过来
|
|
|
+ */
|
|
|
+ if (value instanceof MultiValue) {
|
|
|
+ MultiValue multiValue = (MultiValue) value;
|
|
|
+ List<Object> values = multiValue.getValues();
|
|
|
+ Occur occur = multiValue.isOr() ? Occur.SHOULD : Occur.MUST;
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ for (Object object : values) {
|
|
|
+ booleanQuery2.add(filterIgnoreCase(field, String.valueOf(object)), occur);
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, Occur.FILTER);
|
|
|
+ } else {
|
|
|
+ if (value != null) {
|
|
|
+ booleanQuery.add(filterIgnoreCase(field, String.valueOf(value)), Occur.FILTER);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 排除过滤
|
|
|
+ Map<String, Object> notEqualFilters = pageParams.getNotEqualFilters();
|
|
|
+ if (!CollectionUtils.isEmpty(notEqualFilters)) {
|
|
|
+ Set<Entry<String, Object>> entrySet = notEqualFilters.entrySet();
|
|
|
+ for (Entry<String, Object> entry : entrySet) {
|
|
|
+ String key = entry.getKey();
|
|
|
+ Object value = entry.getValue();
|
|
|
+ String field = key;
|
|
|
+ // 排除多个值
|
|
|
+ if (value instanceof MultiValue) {
|
|
|
+ MultiValue multiValue = (MultiValue) value;
|
|
|
+ List<Object> values = multiValue.getValues();
|
|
|
+ Occur occur = multiValue.isOr() ? Occur.SHOULD : Occur.MUST;
|
|
|
+ BooleanQuery booleanQuery2 = new BooleanQuery();
|
|
|
+ for (Object object : values) {
|
|
|
+ booleanQuery2.add(SearchUtils.regexpQuery(field, String.valueOf(object), false), occur);
|
|
|
+ }
|
|
|
+ booleanQuery.add(booleanQuery2, Occur.MUST_NOT);
|
|
|
+ } else {
|
|
|
+ if (value != null) {
|
|
|
+ booleanQuery.add(SearchUtils.regexpQuery(field, String.valueOf(value), false),
|
|
|
+ Occur.MUST_NOT);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ logger.info(booleanQuery.toString());
|
|
|
+
|
|
|
+ TopDocs topDocs;
|
|
|
+ // 如果页码不为1
|
|
|
+ if (sPage.getPage() > 1) {
|
|
|
+ TopDocs previousTopDocs = null;
|
|
|
+ if (sort != null) {
|
|
|
+ previousTopDocs = indexSearcher.search(booleanQuery, (sPage.getPage() - 1) * sPage.getSize(), sort);
|
|
|
+ } else {
|
|
|
+ previousTopDocs = indexSearcher.search(booleanQuery, (sPage.getPage() - 1) * sPage.getSize());
|
|
|
+ }
|
|
|
+ int totalHits = previousTopDocs.totalHits;
|
|
|
+ ScoreDoc[] previousScoreDocs = previousTopDocs.scoreDocs;
|
|
|
+ // 搜索结果为空时,返回空集
|
|
|
+ if (totalHits == 0) {
|
|
|
+ sPage.setTotalElement(totalHits);
|
|
|
+ int totalPage = (int) Math.ceil(totalHits / (1.0 * sPage.getSize()));
|
|
|
+ sPage.setTotalPage(totalPage);
|
|
|
+ if (totalPage == sPage.getPage()) {
|
|
|
+ sPage.setLast(true);
|
|
|
+ }
|
|
|
+ sPage.setContent(new ArrayList<ScoreDoc>());
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+ // 超出页码数时,返回第1页数据
|
|
|
+ if ((sPage.getPage() - 1) * sPage.getSize() >= totalHits) {
|
|
|
+ sPage.setPage(1);
|
|
|
+ sPage.setFirst(true);
|
|
|
+ if (sort != null) {
|
|
|
+ topDocs = indexSearcher.search(booleanQuery, sPage.getSize(), sort);
|
|
|
+ } else {
|
|
|
+ topDocs = indexSearcher.search(booleanQuery, sPage.getSize());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if (sort != null && sPage.getPage() > 1) {
|
|
|
+ topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], booleanQuery,
|
|
|
+ sPage.getSize(), sort);
|
|
|
+ } else {
|
|
|
+ topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], booleanQuery,
|
|
|
+ sPage.getSize());
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ sPage.setFirst(true);
|
|
|
+ if (sort != null) {
|
|
|
+ topDocs = indexSearcher.search(booleanQuery, sPage.getSize(), sort);
|
|
|
+ } else {
|
|
|
+ topDocs = indexSearcher.search(booleanQuery, sPage.getSize());
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ int totalHits = topDocs.totalHits;
|
|
|
+ // 设置总元素个数、页数等信息
|
|
|
+ sPage.setTotalElement(totalHits);
|
|
|
+ int totalPage = (int) Math.ceil(totalHits / (1.0 * sPage.getSize()));
|
|
|
+ sPage.setTotalPage(totalPage);
|
|
|
+ if (totalPage == sPage.getPage()) {
|
|
|
+ sPage.setLast(true);
|
|
|
+ }
|
|
|
+
|
|
|
+ sPage.setContent(Arrays.asList(topDocs.scoreDocs));
|
|
|
+ return sPage;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取排序字段的类型
|
|
|
+ *
|
|
|
+ * @param s
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private SortField.Type getType(SortPlus s) {
|
|
|
+ if (s == null) {
|
|
|
+ return null;
|
|
|
+ }
|
|
|
+ SortPlus.Type type = s.getType();
|
|
|
+ switch (type) {
|
|
|
+ case STRING:
|
|
|
+ return SortField.Type.STRING;
|
|
|
+ case INT:
|
|
|
+ return SortField.Type.INT;
|
|
|
+ case FLOAT:
|
|
|
+ return SortField.Type.FLOAT;
|
|
|
+ case LONG:
|
|
|
+ return SortField.Type.LONG;
|
|
|
+ case DOUBLE:
|
|
|
+ return SortField.Type.DOUBLE;
|
|
|
+ default:
|
|
|
+ throw new SearchException("排序参数错误:" + type);
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * @return 过滤时不区分大小写
|
|
|
+ */
|
|
|
+ private Query filterIgnoreCase(String field, String keyword){
|
|
|
+ BooleanQuery booleanQuery = new BooleanQuery();
|
|
|
+ booleanQuery.add(SearchUtils.regexpQuery(field, keyword, false), Occur.SHOULD);
|
|
|
+ booleanQuery.add(SearchUtils.regexpQuery(field, keyword.toLowerCase(), false), Occur.SHOULD);
|
|
|
+ return booleanQuery;
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 转换SPage
|
|
|
+ *
|
|
|
+ * @param scoreDocPage
|
|
|
+ * @param clazz
|
|
|
+ * @return
|
|
|
+ */
|
|
|
+ private <T> SPage<T> convertSPage(SPage<ScoreDoc> scoreDocPage, Class<T> clazz) {
|
|
|
+ return new SPage<>(scoreDocPage.getTotalPage(), scoreDocPage.getTotalElement(), scoreDocPage.getPage(),
|
|
|
+ scoreDocPage.getSize(), scoreDocPage.isFirst(), scoreDocPage.isLast());
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取指定的可以搜索的字段列名
|
|
|
+ *
|
|
|
+ * @param tableName
|
|
|
+ * 单据类型
|
|
|
+ * @param keywordSearchColumns
|
|
|
+ * keyword模糊搜索的字段名称
|
|
|
+ * @return 可以搜索的字段列名
|
|
|
+ */
|
|
|
+ private List<String> getKeywordFields(Table_name tableName, List<String> keywordSearchColumns) {
|
|
|
+ List<String> result = new ArrayList<>();
|
|
|
+ for (String keywordSearchColumn : keywordSearchColumns) {
|
|
|
+ // 返回之前先拼接索引域名
|
|
|
+ result.add(ClassAndTableNameUtils.combineField(tableName, keywordSearchColumn));
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private SPage<Document> getProductDocuments(Long enUU, String keyword, Integer page, Integer size, String type) throws IOException {
|
|
|
// if (SearchUtils.isKeywordInvalid(keyword)) {
|
|
|
// throw new IllegalArgumentException("搜索关键词无效:" + keyword);
|
|
|
// }
|