Browse Source

增加b2b搜索通用方法

wangyc 6 years ago
parent
commit
20b924d9ac

+ 15 - 0
mall-search/src/main/java/com/uas/search/constant/SearchConstants.java

@@ -8,6 +8,21 @@ package com.uas.search.constant;
  */
 public class SearchConstants {
 
+	/**
+	 * 用来对搜索结果进行排序,该键代表排序的排序方式
+	 */
+	public static final String SORT_KEY = "sort";
+
+	/**
+	 * 单据搜索时,若限定时间范围,可以键值对的形式传递参数,该键代表开始时间
+	 */
+	public static final String FROM_DATE_KEY = "fromDate";
+
+	/**
+	 * 单据搜索时,若限定时间范围,可以键值对的形式传递参数,该键代表截止时间
+	 */
+	public static final String END_DATE_KEY = "endDate";
+
 	/**
 	 * 默认搜索的最大的记录条数
 	 */

+ 62 - 0
mall-search/src/main/java/com/uas/search/constant/model/MultiValue.java

@@ -0,0 +1,62 @@
+package com.uas.search.constant.model;
+
+import java.io.Serializable;
+import java.util.List;
+
+/**
+ * 搜索时,一个字段可能对应多个值
+ * 
+ * @author sunyj
+ * @since 2016年11月28日 上午8:47:54
+ */
+public class MultiValue implements Serializable {
+
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 字段对应的多个值
+	 */
+	private List<Object> values;
+
+	/**
+	 * 多个值之间默认是AND逻辑关系,若该成员变量为true,则为OR逻辑关系
+	 */
+	private boolean or = false;
+
+	public MultiValue() {
+		super();
+	}
+
+	public MultiValue(List<Object> values) {
+		super();
+		this.values = values;
+	}
+
+	public MultiValue(List<Object> values, boolean or) {
+		super();
+		this.values = values;
+		this.or = or;
+	}
+
+	public List<Object> getValues() {
+		return values;
+	}
+
+	public void setValues(List<Object> values) {
+		this.values = values;
+	}
+
+	public boolean isOr() {
+		return or;
+	}
+
+	public void setOr(boolean or) {
+		this.or = or;
+	}
+
+	@Override
+	public String toString() {
+		return "MultiValueField [values=" + values + ", or=" + or + "]";
+	}
+
+}

+ 52 - 0
mall-search/src/main/java/com/uas/search/constant/model/PageParams.java

@@ -2,6 +2,7 @@ package com.uas.search.constant.model;
 
 import java.io.Serializable;
 import java.util.HashMap;
+import java.util.List;
 import java.util.Map;
 
 /**
@@ -34,6 +35,14 @@ public class PageParams implements Serializable {
 	 */
 	private Sort sort;
 
+	/**
+	 * keyword模糊搜索的字段名称
+	 */
+	private List<String> keywordSearchColumns;
+
+	private Map<String, Object> commonFilters;
+	private Map<String, Object> notEqualFilters;
+
 	public PageParams() {
 	}
 
@@ -48,6 +57,12 @@ public class PageParams implements Serializable {
 		this.size = size;
 	}
 
+	public PageParams(int page, int size, Map<String, Object> filters) {
+		this.page = page;
+		this.size = size;
+		this.commonFilters = filters;
+	}
+
 	/**
 	 * @return 页码
 	 */
@@ -70,6 +85,43 @@ public class PageParams implements Serializable {
 		this.size = size;
 	}
 
+	public List<String> getKeywordSearchColumns() {
+		return keywordSearchColumns;
+	}
+
+	public void setKeywordSearchColumns(List<String> keywordSearchColumns) {
+		this.keywordSearchColumns = keywordSearchColumns;
+	}
+
+	public Map<String, Object> getCommonFilters() {
+		return commonFilters;
+	}
+
+	public void setCommonFilters(Map<String, Object> commonFilters) {
+		this.commonFilters = commonFilters;
+	}
+
+	public void commmonFilter(String key, Object value) {
+		if (this.commonFilters == null) {
+			this.commonFilters = new HashMap<String, Object>();
+		}
+		this.commonFilters.put(key, value);
+	}
+
+	public void removeCommonFilter(String key) {
+		if (this.commonFilters != null) {
+			this.commonFilters.remove(key);
+		}
+	}
+
+	public Map<String, Object> getNotEqualFilters() {
+		return notEqualFilters;
+	}
+
+	public void setNotEqualFilters(Map<String, Object> notEqualFilters) {
+		this.notEqualFilters = notEqualFilters;
+	}
+
 	/**
 	 * @return 过滤
 	 */

+ 128 - 0
mall-search/src/main/java/com/uas/search/constant/model/SortPlus.java

@@ -0,0 +1,128 @@
+package com.uas.search.constant.model;
+
+import java.io.Serializable;
+
+/**
+ * 排序
+ * 
+ * @author sunyj
+ * @since 2016年11月23日 下午1:47:44
+ */
+public class SortPlus implements Serializable {
+
+	/**
+	 * 序列号
+	 */
+	private static final long serialVersionUID = 1L;
+
+	/**
+	 * 所排序的字段的类型
+	 */
+	public static enum Type {
+		/** 字段为字符串 */
+		STRING,
+
+		/** 字段为Integer */
+		INT,
+
+		/** 字段为Float */
+		FLOAT,
+
+		/** 字段为Long */
+		LONG,
+
+		/** 字段为Double */
+		DOUBLE;
+	}
+
+	/**
+	 * 排序的字段名称(在表中的列名)
+	 */
+	private String field;
+
+	/**
+	 * 排序默认为降序,若为true,则为升序
+	 */
+	private boolean reverse = false;
+
+	/**
+	 * 排序的字段的类型,默认为String
+	 */
+	private Type type = Type.STRING;
+
+	/**
+	 * 若排序字段的值为空,设置一个默认值
+	 */
+	private Object missingValue;
+
+	public SortPlus(String field, boolean reverse, Type type, Object missingValue) {
+		super();
+		this.field = field;
+		this.reverse = reverse;
+		this.type = type;
+		this.missingValue = missingValue;
+	}
+
+	public SortPlus(String field, boolean reverse, Object missingValue) {
+		super();
+		this.field = field;
+		this.reverse = reverse;
+		this.missingValue = missingValue;
+	}
+
+	public SortPlus(String field, Type type, Object missingValue) {
+		super();
+		this.field = field;
+		this.type = type;
+		this.missingValue = missingValue;
+	}
+
+	public SortPlus(String field, Object missingValue) {
+		super();
+		this.field = field;
+		this.missingValue = missingValue;
+	}
+
+	public SortPlus() {
+		super();
+	}
+
+	public String getField() {
+		return field;
+	}
+
+	public void setField(String field) {
+		this.field = field;
+	}
+
+	public boolean isReverse() {
+		return reverse;
+	}
+
+	public void setReverse(boolean reverse) {
+		this.reverse = reverse;
+	}
+
+	public Type getType() {
+		return type;
+	}
+
+	public void setType(Type type) {
+		this.type = type;
+	}
+
+	public Object getMissingValue() {
+		return missingValue;
+	}
+
+	public void setMissingValue(Object missingValue) {
+		this.missingValue = missingValue;
+	}
+
+	@Override
+	public String toString() {
+		return "Sort [field=" + field + ", reverse=" + reverse + ", type=" + type + ", missingValue=" + missingValue
+				+ "]";
+	}
+
+}

+ 16 - 0
mall-search/src/main/java/com/uas/search/controller/SearchController.java

@@ -62,6 +62,22 @@ public class SearchController {
 		return searchService.getProductIds(enUU, keyword, page, size, type);
 	}
 
+	/**
+	 * 查询所有物料(b2b)
+	 * @param keyword 关键词
+	 * @param page  页码
+	 * @param size  尺寸
+	 * @return idPage
+	 * @throws IOException 输入异常
+	 */
+	@RequestMapping("/productIds/b2b")
+	@ResponseBody
+	public SPage<Long> searchProductIdsB2B(String keyword, String tableName, Integer page, Integer size,
+		String keywordSearchColumns, String filters, String multiValueField, String sort, String notEqualFilters,
+		String multiValueNotField) throws IOException {
+		return searchService.getProductIdsB2B(keyword, tableName, page, size, keywordSearchColumns, filters, multiValueField, sort, notEqualFilters, multiValueNotField);
+	}
+
 	/**
 	 * 获取标准型号联想词
 	 * @param keyword 关键词

+ 90 - 0
mall-search/src/main/java/com/uas/search/exception/SearchException.java

@@ -0,0 +1,90 @@
+package com.uas.search.exception;
+
+/**
+ * 搜索异常
+ * 
+ * @author sunyj
+ * @since 2016年9月30日 下午3:32:48
+ */
+public class SearchException extends RuntimeException {
+
+	private static final long serialVersionUID = 1L;
+
+	private String message;
+
+	/**
+	 * 详细信息
+	 */
+	private String detailedMessage;
+
+	public SearchException(String message) {
+		this.message = message;
+	}
+
+	public SearchException(Throwable e) {
+		this.message = getMessage(e);
+	}
+
+	/**
+	 * 获取异常及其Cause拼接成的字符串
+	 * 
+	 * @param e
+	 *            异常
+	 * @return 拼接后的结果
+	 */
+	public String getMessage(Throwable e) {
+		StringBuilder sb = new StringBuilder(e.toString());
+		if (e.getCause() != null) {
+			sb.append("\nCaused by: ").append(getMessage(e.getCause()));
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 获取异常及其Cause的StackTrace拼接成的字符串
+	 * 
+	 * @param e
+	 *            异常
+	 * @return 拼接后的结果
+	 */
+	public String getDetailedMessage(Throwable e) {
+		StringBuilder sb = new StringBuilder(e.toString());
+		StackTraceElement[] stackTraceElements = e.getStackTrace();
+		for (StackTraceElement stackTraceElement : stackTraceElements) {
+			sb.append("\n\t").append(stackTraceElement.toString());
+		}
+		if (e.getCause() != null) {
+			sb.append("\nCaused by: ").append(getDetailedMessage(e.getCause()));
+		}
+		return sb.toString();
+	}
+
+	/**
+	 * 获取异常及其Cause的StackTrace用以设置详细信息
+	 * 
+	 * @param e
+	 *            异常
+	 * @return 本SearchException对象
+	 */
+	public SearchException setDetailedMessage(Throwable e) {
+		this.detailedMessage = getDetailedMessage(e);
+		return this;
+	}
+
+	public String getMessage() {
+		return message;
+	}
+
+	public void setMessage(String message) {
+		this.message = message;
+	}
+
+	public String getDetailedMessage() {
+		return detailedMessage;
+	}
+
+	public void setDetailedMessage(String detailedMessage) {
+		this.detailedMessage = detailedMessage;
+	}
+
+}

+ 8 - 0
mall-search/src/main/java/com/uas/search/model/ProductUsersSimpleInfo.java

@@ -3,6 +3,8 @@ package com.uas.search.model;
 import java.io.Serializable;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
 import javax.persistence.CascadeType;
 import javax.persistence.Column;
 import javax.persistence.Entity;
@@ -91,6 +93,12 @@ public class ProductUsersSimpleInfo implements Serializable, RowMapper{
         this.product = product;
     }
 
+    public static List<String> getKeywordFields() {
+        List<String> fields = new ArrayList<>();
+        fields.add("pu_prid");
+        return fields;
+    }
+
     @Override
     public String toString() {
         return "ProductUsersSimpleInfo{" +

+ 22 - 1
mall-search/src/main/java/com/uas/search/model/V_Products.java

@@ -1,12 +1,14 @@
 package com.uas.search.model;
 
+import java.io.Serializable;
 import java.sql.ResultSet;
 import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.List;
 import javax.persistence.Column;
 import javax.persistence.Entity;
 import javax.persistence.Id;
 import javax.persistence.Table;
-import java.io.Serializable;
 import org.springframework.jdbc.core.RowMapper;
 
 /**
@@ -269,6 +271,25 @@ public class V_Products implements RowMapper, Serializable {
         this.isPubsale = isPubsale;
     }
 
+    /**
+     * 获取所有可以搜索的字段列名(索引的field名)
+     *
+     * @return 可以搜索的字段列名
+     */
+    public static List<String> getKeywordFields() {
+        List<String> fields = new ArrayList<>();
+        fields.add("pr_code");
+        fields.add("pr_title");
+        fields.add("pr_spec");
+        fields.add("pr_brand");
+        fields.add("pr_cmpcode");
+        fields.add("pr_kind");
+        fields.add("pr_pbrand");
+        fields.add("pr_pbranden");
+        fields.add("pr_pcmpcode");
+        return fields;
+    }
+
     @Override
     public String toString() {
         return "V_Products{" +

+ 93 - 2
mall-search/src/main/java/com/uas/search/service/SearchService.java

@@ -5,8 +5,13 @@ import com.uas.search.constant.model.CollectField;
 import com.uas.search.constant.model.PageParams;
 import com.uas.search.constant.model.PageParams.FilterField;
 import com.uas.search.constant.model.SPage;
-import com.uas.search.model.*;
-
+import com.uas.search.exception.SearchException;
+import com.uas.search.model.Brand;
+import com.uas.search.model.Component;
+import com.uas.search.model.Goods;
+import com.uas.search.model.Kind;
+import com.uas.search.model.PCBGoods;
+import com.uas.search.model.V_Products;
 import java.io.IOException;
 import java.util.List;
 import java.util.Map;
@@ -620,6 +625,65 @@ public interface SearchService {
 	 */
 	SPage<Long> getProductIds(Long enUU, String keyword, Integer page, Integer size, String type) throws IOException;
 
+	/**
+	 * 查询物料(B2B)
+	 * @param keyword 关键词
+	 * @param page  页码
+	 * @param size  尺寸
+	 * @return idPage
+	 * @throws IOException 输入异常
+	 */
+	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;
+
+	/**
+	 * 根据关键词、索引名称、状态码搜索单据id
+	 *
+	 * @param keyword
+	 *            可为空,模糊搜索关键词
+	 * @param tableName
+	 *            不为空,单据类型
+	 * @param pageParams
+	 *            可为空,翻页、过滤、排序、排除过滤等信息
+	 *            <p>
+	 *            关于过滤,通过键值对指定过滤条件,值的类型由键决定:
+	 *            </p>
+	 *
+	 *            <table border=1 cellpadding=5 cellspacing=0 summary=
+	 *            "Fields and types">
+	 *            <tr>
+	 *            <th>Description</th>
+	 *            <th>Field</th>
+	 *            <th>Type</th>
+	 *            </tr>
+	 *            <tr>
+	 *            <td>状态、所属企业uu、其他状态(如已采纳、未采纳等)</td>
+	 *            <td>键为数据库表中相应的字段名称</td>
+	 *            <td>值为字段对应的值,若值有多个,则使用
+	 *            {@link com.uas.search.constant.model.MultiValue}</td>
+	 *            </tr>
+	 *            <tr>
+	 *            <td>开始时间、截止时间, 这两个参数用于对时间范围进行筛选</td>
+	 *            <td>{@link com.uas.search.constant.SearchConstants}</td>
+	 *            <td>Long</td>
+	 *            </tr>
+	 *            <tr>
+	 *            <td>排序方式</td>
+	 *            <td>{@link com.uas.search.constant.SearchConstants}</td>
+	 *            <td>List({@link com.uas.search.constant.model.Sort})</td>
+	 *            </tr>
+	 *            </table>
+	 *
+	 *            <p>
+	 *            关于排除过滤,键为数据库表中相应的字段名称,值为需排除的值,若值有多个,则使用
+	 *            {@link com.uas.search.constant.model.MultiValue}
+	 *            </p>
+	 * @return 单据id
+	 * @throws SearchException
+	 */
+	SPage<Long> searchIds(String keyword, Table_name tableName, PageParams pageParams) throws SearchException;
+
 	/**
 	 * 根据id获取物料
 	 *
@@ -643,4 +707,31 @@ public interface SearchService {
 	 * @return
 	 */
 	List<Map<String,Object>> getSimilarKind(String keyword, Integer size);
+
+	public enum Table_name {
+		/**
+		 * 物料
+		 */
+		V$PRODUCT$PRIVATE("v$product$private"),
+
+		/**
+		 * 个人物料
+		 */
+		PRODUCT$USERS("product$users");
+
+		private Table_name(String phrase) {
+			this.phrase = phrase;
+		}
+
+		private String phrase;
+
+		public String value() {
+			return phrase;
+		}
+
+		@Override
+		public String toString() {
+			return phrase;
+		}
+	}
 }

+ 392 - 1
mall-search/src/main/java/com/uas/search/service/impl/SearchServiceImpl.java

@@ -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);
 //		}

+ 346 - 0
mall-search/src/main/java/com/uas/search/util/ClassAndTableNameUtils.java

@@ -0,0 +1,346 @@
+package com.uas.search.util;
+
+import com.uas.search.exception.SearchException;
+import com.uas.search.model.ProductUsersSimpleInfo;
+import com.uas.search.model.V_Products;
+import com.uas.search.service.SearchService.Table_name;
+import java.lang.annotation.Annotation;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.List;
+import javax.persistence.Column;
+import javax.persistence.Table;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+
+/**
+ * 实体类与单据类型的工具类
+ * 
+ * @author sunyj
+ * @since 2016年11月10日 下午2:18:10
+ */
+public class ClassAndTableNameUtils {
+
+	private static Logger logger = LoggerFactory.getLogger(ClassAndTableNameUtils.class);
+
+	/**
+	 * 需要建立索引的所有实体类
+	 */
+	private static List<Class<?>> entityClasses = new ArrayList<>();
+
+	/**
+	 * 需要建立索引的所有实体类的Dao类
+	 */
+	private static List<Class<?>> entityDaoClasses = new ArrayList<>();
+
+	static {
+		entityClasses.add(V_Products.class);
+		entityClasses.add(ProductUsersSimpleInfo.class);
+
+		// entityClasses和entityDaoClasses对于表的添加顺序必须一致
+		entityDaoClasses.add(V_Products.class);
+		entityDaoClasses.add(ProductUsersSimpleInfo.class);
+	}
+
+	/**
+	 * 获取所有(需要建立索引)的实体类
+	 * 
+	 * @return
+	 */
+	public static List<Class<?>> getEntityClasses() {
+		return entityClasses;
+	}
+
+	/**
+	 * 获取所有(需要建立索引)的实体类的Dao类
+	 * 
+	 * @return
+	 */
+	public static List<Class<?>> getEntityDaoClasses() {
+		return entityDaoClasses;
+	}
+
+	/**
+	 * 获取所有(需要建立索引)的单据类型
+	 * 
+	 * @return
+	 */
+	public static List<Table_name> getTableNames() {
+		List<Table_name> tableNames = new ArrayList<>();
+		for (Class<?> clazz : entityClasses) {
+			tableNames.add(toTableName(clazz));
+		}
+		return tableNames;
+	}
+
+	/**
+	 * 获取实体类clazz对应的Dao
+	 * 
+	 * @param clazz
+	 *            实体类型
+	 * @return 实体类的Dao
+	 * @throws ClassNotFoundException
+	 */
+	@SuppressWarnings("unchecked")
+	public static <T> JpaRepository<T, Long> getDao(Class<T> clazz) {
+		if (checkEntityClass(clazz)) {
+			return (JpaRepository<T, Long>) ContextUtils
+					.getBean(getEntityDaoClasses().get(getEntityClasses().indexOf(clazz)));
+		}
+		throw new SearchException("并非合法的实体类:" + clazz.getName());
+	}
+
+	/**
+	 * 检查是否是合法的实体类(需要建立索引的实体类才是合法的)
+	 * 
+	 * @param clazz
+	 *            实体类型
+	 * @return true,如果实体类合法
+	 */
+	private static boolean checkEntityClass(Class<?> clazz) {
+		if (clazz != null) {
+			List<Class<?>> clazzes = getEntityClasses();
+			if (clazzes.contains(clazz)) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	/**
+	 * 根据单据类型获取每个单据可以搜索的字段列名(索引的field名)
+	 * 
+	 * @param tableName
+	 *            单据类型
+	 * @return 可以搜索的字段列名
+	 */
+	public static List<String> getKeywordFields(Table_name tableName) {
+		return getKeywordFields(toClass(tableName));
+	}
+
+	/**
+	 * 根据实体类名获取每个单据可以搜索的字段列名(索引的field名)
+	 * 
+	 * @param clazz
+	 *            实体类型
+	 * @return 可以搜索的字段列名
+	 */
+	@SuppressWarnings("unchecked")
+	public static List<String> getKeywordFields(Class<?> clazz) {
+		if (checkEntityClass(clazz)) {
+			try {
+				Method method = clazz.getDeclaredMethod("getKeywordFields");
+				List<String> keywordFields = (List<String>) method.invoke(clazz);
+				List<String> result = new ArrayList<>();
+				// 返回之前先拼接索引域名
+				for (String keywordField : keywordFields) {
+					result.add(keywordField);
+				}
+				return result;
+			} catch (NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException
+					| InvocationTargetException e) {
+				throw new SearchException(e).setDetailedMessage(e);
+			}
+		}
+		throw new SearchException("并非合法的实体类:" + clazz.getName());
+	}
+
+	/**
+	 * 获取每个单据的id字段的列名(索引的field名)
+	 * 
+	 * @param tableName
+	 *            单据类型
+	 * @return id字段的列名
+	 */
+	public static String getIdField(Table_name tableName) {
+		return getIdField(toClass(tableName));
+	}
+
+	/**
+	 * 根据实体类型获取每个单据的id字段的列名(索引的field名)
+	 * 
+	 * @param clazz
+	 *            实体类型
+	 * @return id字段的列名
+	 */
+	public static String getIdField(Class<?> clazz) {
+		if (checkEntityClass(clazz)) {
+			try {
+				Field field = clazz.getDeclaredField("id");
+				// 返回之前先拼接索引域名
+				return field.getAnnotation(Column.class).name();
+			} catch (NoSuchFieldException | SecurityException | IllegalArgumentException e) {
+				logger.error("实体类中没有ID_FIELD变量:" + clazz.getName());
+				throw new SearchException(e).setDetailedMessage(e);
+			}
+		}
+		throw new SearchException("并非合法的实体类:" + clazz.getName());
+	}
+
+	/**
+	 * 获取每个单据的date(只进行时间范围筛选,不进行搜索)字段的列名(索引的field名)
+	 * 
+	 * @param tableName
+	 *            单据类型
+	 * @return date字段的列名
+	 */
+	public static String getDateField(Table_name tableName) {
+		return getDateField(toClass(tableName));
+	}
+
+	/**
+	 * 根据实体类型获取每个单据的date(只进行时间范围筛选,不进行搜索)字段的列名(索引的field名)
+	 * 
+	 * @param clazz
+	 *            实体类型
+	 * @return date字段的列名
+	 */
+	public static String getDateField(Class<?> clazz) {
+		if (checkEntityClass(clazz)) {
+			try {
+				Field field = clazz.getDeclaredField("DATE_FIELD");
+				// 返回之前先拼接索引域名
+				return field.get(clazz).toString();
+			} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
+				logger.error("实体类中没有DATE_FIELD变量:" + clazz.getName());
+				return null;
+			}
+		}
+		throw new SearchException("并非合法的实体类:" + clazz.getName());
+	}
+
+	/**
+	 * 获取实体id字段的值
+	 * 
+	 * @param object
+	 *            实体对象
+	 * @return id
+	 */
+	public static <T> Long getId(T object) {
+		Class<?> clazz = object.getClass();
+		if (checkEntityClass(clazz)) {
+			try {
+				Field field = clazz.getDeclaredField("id");
+				field.setAccessible(true);
+				return (Long) field.get(object);
+			} catch (NoSuchFieldException | SecurityException | IllegalArgumentException | IllegalAccessException e) {
+				logger.error("实体类中没有id变量:" + clazz.getName());
+				throw new SearchException(e).setDetailedMessage(e);
+			}
+		}
+		throw new SearchException("并非合法的实体类:" + clazz.getName());
+	}
+
+	/**
+	 * 创建对象
+	 * 
+	 * @param <T>
+	 * @param clazz
+	 *            所创建对象的类型
+	 * @param id
+	 *            对象的id
+	 * @return 创建的对象
+	 */
+	public static <T> T createObject(Class<T> clazz, Long id) {
+		if (checkEntityClass(clazz)) {
+			try {
+				T element = clazz.newInstance();
+				Field field = clazz.getDeclaredField("id");
+				field.setAccessible(true);
+				field.set(element, id);
+				return element;
+			} catch (IllegalArgumentException e) {
+				logger.error("", e);
+			} catch (IllegalAccessException e) {
+				logger.error("", e);
+			} catch (NoSuchFieldException e) {
+				logger.error("", e);
+			} catch (SecurityException e) {
+				logger.error("", e);
+			} catch (InstantiationException e) {
+				logger.error("", e);
+			}
+		}
+		logger.error("创建对象失败:clazz=" + clazz + ",id=" + id);
+		return null;
+	}
+
+	/**
+	 * 根据实体类获取对应的单据类型
+	 * 
+	 * @param clazz
+	 *            实体类型
+	 * @return 单据类型
+	 */
+	public static Table_name toTableName(Class<?> clazz) {
+		if (checkEntityClass(clazz)) {
+			try {
+				Annotation table = clazz.getAnnotation(Table.class);
+				return Table_name.valueOf(((Table) table).name().toUpperCase());
+			} catch (SecurityException | IllegalArgumentException e) {
+				logger.error("实体类中没有TABLE_NAME变量:" + clazz.getName());
+				throw new SearchException(e).setDetailedMessage(e);
+			}
+		}
+		throw new SearchException("并非合法的实体类:" + clazz.getName());
+	}
+
+	/**
+	 * 根据单据类型获取对应的实体类型
+	 * 
+	 * @param tableName
+	 *            单据类型
+	 * @return 实体类型
+	 */
+	public static Class<?> toClass(Table_name tableName) {
+		if (tableName == null) {
+			return null;
+		}
+		List<Class<?>> classes = getEntityClasses();
+		for (Class<?> clazz : classes) {
+			if (tableName == toTableName(clazz)) {
+				return clazz;
+			}
+		}
+		throw new SearchException("并非合法的表:" + tableName.value());
+	}
+
+	/**
+	 * 获取索引域名,因为不同的表可能会有相同名称的域(即数据库表的列名完全相同),因此,不能用字段名称作为索引的Field域名,
+	 * 需要拼接上表名才能保证每个索引域名独一无二
+	 * 
+	 * @param tableName
+	 *            表名
+	 * @param field
+	 *            数据库表中字段名称
+	 * @return 拼接后的索引域名
+	 */
+	public static String combineField(Table_name tableName, String field) {
+		if (tableName == null) {
+			return null;
+		}
+		return combineField(tableName.value(), field);
+	}
+
+	/**
+	 * 获取索引域名,因为不同的表可能会有相同名称的域(即数据库表的列名完全相同),因此,不能用字段名称作为索引的Field域名,
+	 * 需要拼接上表名才能保证每个索引域名独一无二
+	 * 
+	 * @param tableName
+	 *            表名
+	 * @param field
+	 *            数据库表中字段名称
+	 * @return 拼接后的索引域名
+	 */
+	public static String combineField(String tableName, String field) {
+		if (StringUtils.isEmpty(tableName) || StringUtils.isEmpty(field)) {
+			return null;
+		}
+		return tableName.toLowerCase() + "." + field.toLowerCase();
+	}
+
+}

+ 41 - 0
mall-search/src/main/java/com/uas/search/util/SearchUtils.java

@@ -490,4 +490,45 @@ public class SearchUtils {
 		}
 		return luceneProperties.getDataDir() + "/" + tableName;
 	}
+
+	/**
+	 * 对搜索词进行分词后组合得到RegexpQuery
+	 *
+	 * @param field
+	 *            搜索的域名
+	 * @param keyword
+	 *            搜索关键词
+	 * @param tokenized
+	 *            可为空,模糊搜索时,是否对关键词进行分词,为空时,不分词
+	 * @return
+	 */
+	// TODO 如果用正则表达式进行搜索,建索引时就不需要再分词(分词的字段可能会导致边界问题) <- 可以分词
+	public static Query regexpQuery(String field, String keyword, Boolean tokenized) {
+		if (StringUtils.isEmpty(field)) {
+			return null;
+		}
+		if (field == null) {
+			field = "";
+		}
+		// 关键词为空或不分词,直接返回单个RegexpQuery
+		if (StringUtils.isEmpty(keyword) || tokenized == null || !tokenized.booleanValue()) {
+			return new RegexpQuery(new Term(field, ".*" + keyword + ".*"));
+		}
+		BooleanQuery booleanQuery = new BooleanQuery();
+		Analyzer analyzer = new IKAnalyzer(true);
+		try {
+			TokenStream tokenStream = analyzer.tokenStream(field, keyword);
+			tokenStream.reset();
+			CharTermAttribute cta = tokenStream.addAttribute(CharTermAttribute.class);
+			while (tokenStream.incrementToken()) {
+				booleanQuery.add(new RegexpQuery(new Term(field, ".*" + cta.toString() + ".*")),
+					BooleanClause.Occur.MUST);
+			}
+			tokenStream.close();
+			analyzer.close();
+		} catch (IOException e) {
+			logger.error("", e);
+		}
+		return booleanQuery;
+	}
 }