Browse Source

初始导入

sunyj 9 years ago
parent
commit
e6514cf1a6
43 changed files with 3224 additions and 1 deletions
  1. 3 1
      pom.xml
  2. 90 0
      search-api-b2b/src/main/java/com/uas/search/b2b/exception/SearchException.java
  3. 50 0
      search-api-b2b/src/main/java/com/uas/search/b2b/model/PageParams.java
  4. 89 0
      search-api-b2b/src/main/java/com/uas/search/b2b/model/SPage.java
  5. 65 0
      search-api-b2b/src/main/java/com/uas/search/b2b/service/SearchService.java
  6. 51 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/controller/IndexController.java
  7. 45 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/controller/SearchController.java
  8. 110 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/core/advice/ExceptionHandlerAdvice.java
  9. 17 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/dao/MakeOrderSimpleInfoDao.java
  10. 17 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/dao/PurchaseOrderSimpleInfoDao.java
  11. 190 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/jms/AQListener.java
  12. 83 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/jms/QueueMessageParser.java
  13. 58 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/jms/QueueMessageTypeFactory.java
  14. 68 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/model/EnterpriseSimpleInfo.java
  15. 132 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/model/MakeOrderSimpleInfo.java
  16. 86 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/model/ParsedQueueMessage.java
  17. 69 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/model/ProductSimpleInfo.java
  18. 67 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/model/PurchaseOrderItemSimpleInfo.java
  19. 137 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/model/PurchaseOrderSimpleInfo.java
  20. 52 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/service/IndexService.java
  21. 289 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/service/impl/IndexServiceImpl.java
  22. 138 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/service/impl/SearchServiceImpl.java
  23. 19 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/support/ApplicationContextRegister.java
  24. 163 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/support/IndexSearcherManager.java
  25. 66 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/support/IndexWriterManager.java
  26. 84 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/util/DocumentToObjectUtils.java
  27. 107 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/util/ObjectToDocumentUtils.java
  28. 36 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/util/SearchConstants.java
  29. 270 0
      search-console-b2b/src/main/java/com/uas/search/console/b2b/util/SearchUtils.java
  30. 2 0
      search-console-b2b/src/main/resources/dev/dubbo.properties
  31. 12 0
      search-console-b2b/src/main/resources/dev/jdbc.properties
  32. 15 0
      search-console-b2b/src/main/resources/dev/logging.properties
  33. 127 0
      search-console-b2b/src/main/resources/spring/applicationContext.xml
  34. 8 0
      search-console-b2b/src/main/resources/spring/ehcache.xml
  35. 270 0
      search-console-b2b/src/main/resources/spring/ehcache.xsd
  36. 11 0
      search-console-b2b/src/main/resources/spring/jpa.xml
  37. 18 0
      search-console-b2b/src/main/resources/spring/provider.xml
  38. 39 0
      search-console-b2b/src/main/webapp/WEB-INF/views/console.html
  39. 27 0
      search-console-b2b/src/main/webapp/WEB-INF/views/fileUpload.html
  40. 11 0
      search-console-b2b/src/main/webapp/WEB-INF/views/index.html
  41. 17 0
      search-console-b2b/src/main/webapp/resources/css/console.css
  42. 15 0
      search-console-b2b/src/main/webapp/resources/css/fileUpload.css
  43. 1 0
      search-console-b2b/src/main/webapp/resources/lib/jquery/jquery.min.js

+ 3 - 1
pom.xml

@@ -5,7 +5,7 @@
 
 	<groupId>com.uas.search</groupId>
 	<artifactId>search-parent</artifactId>
-	<version>0.1.6</version>
+	<version>${project.version}</version>
 	<packaging>pom</packaging>
 
 	<name>search-parent</name>
@@ -243,6 +243,8 @@
 	<modules>
 		<module>search-api</module>
 		<module>search-console</module>
+		<module>search-api-b2b</module>
+		<module>search-console-b2b</module>
 	</modules>
 	<distributionManagement>
 		<!-- 发布release仓库 -->

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

@@ -0,0 +1,90 @@
+package com.uas.search.b2b.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;
+	}
+
+}

+ 50 - 0
search-api-b2b/src/main/java/com/uas/search/b2b/model/PageParams.java

@@ -0,0 +1,50 @@
+package com.uas.search.b2b.model;
+
+import java.io.Serializable;
+
+/**
+ * 分页参数
+ * 
+ * @author suntg
+ * @since 2016年8月3日下午9:10:47
+ */
+public class PageParams implements Serializable {
+
+	/**
+	 * 序列号
+	 */
+	private static final long serialVersionUID = 1L;
+	private int page;
+	private int size;
+
+	public PageParams() {
+
+	}
+
+	public PageParams(int page, int size) {
+		this.page = page;
+		this.size = size;
+	}
+
+	public int getPage() {
+		return page;
+	}
+
+	public void setPage(int page) {
+		this.page = page;
+	}
+
+	public int getSize() {
+		return size;
+	}
+
+	public void setSize(int size) {
+		this.size = size;
+	}
+
+	@Override
+	public String toString() {
+		return "PageParams [page=" + page + ", size=" + size + "]";
+	}
+
+}

+ 89 - 0
search-api-b2b/src/main/java/com/uas/search/b2b/model/SPage.java

@@ -0,0 +1,89 @@
+package com.uas.search.b2b.model;
+
+import java.io.Serializable;
+import java.util.List;
+
+public class SPage<T> implements Serializable {
+
+	/**
+	 * 
+	 */
+	private static final long serialVersionUID = 1L;
+
+	private int totalPage;
+
+	private long totalElement;
+
+	private int page;
+
+	private int size;
+
+	private boolean first;
+
+	private boolean last;
+
+	private List<T> content;
+
+	public int getTotalPage() {
+		return totalPage;
+	}
+
+	public void setTotalPage(int totalPage) {
+		this.totalPage = totalPage;
+	}
+
+	public long getTotalElement() {
+		return totalElement;
+	}
+
+	public void setTotalElement(long totalElement) {
+		this.totalElement = totalElement;
+	}
+
+	public int getPage() {
+		return page;
+	}
+
+	public void setPage(int page) {
+		this.page = page;
+	}
+
+	public int getSize() {
+		return size;
+	}
+
+	public void setSize(int size) {
+		this.size = size;
+	}
+
+	public boolean isFirst() {
+		return first;
+	}
+
+	public void setFirst(boolean first) {
+		this.first = first;
+	}
+
+	public boolean isLast() {
+		return last;
+	}
+
+	public void setLast(boolean last) {
+		this.last = last;
+	}
+
+	public List<T> getContent() {
+		return content;
+	}
+
+	public void setContent(List<T> content) {
+		this.content = content;
+	}
+
+	@Override
+	public String toString() {
+		return "SPage [totalPage=" + totalPage + ", totalElement=" + totalElement + ", page=" + page + ", size=" + size
+				+ ", first=" + first + ", last=" + last + ", content=" + content + "]";
+	}
+
+}

+ 65 - 0
search-api-b2b/src/main/java/com/uas/search/b2b/service/SearchService.java

@@ -0,0 +1,65 @@
+package com.uas.search.b2b.service;
+
+import com.uas.search.b2b.exception.SearchException;
+import com.uas.search.b2b.model.PageParams;
+import com.uas.search.b2b.model.SPage;
+
+/**
+ * B2B商务平台搜索服务的接口
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 上午9:52:25
+ */
+public interface SearchService {
+	/**
+	 * 单据类型
+	 * 
+	 * @author sunyj
+	 * @since 2016年11月9日 上午10:22:01
+	 */
+	public enum Table_name {
+		/**
+		 * 买家采购订单
+		 */
+		PURC$ORDERS("PURC$ORDERS"),
+		/**
+		 * 买家委外加工单
+		 */
+		MAKE$ORDERS("MAKE$ORDERS");
+
+		// TODO 其他表
+
+		private Table_name(String phrase) {
+			this.phrase = phrase;
+		}
+
+		private String phrase;
+
+		public String value() {
+			return phrase;
+		}
+
+		@Override
+		public String toString() {
+			return phrase;
+		}
+	}
+
+	/**
+	 * 根据关键词、单据类型、状态码搜索单据id
+	 * 
+	 * @param keyword
+	 *            不为空,模糊搜索关键词,可以是:单据编号、供应商uu(采购)、供应商名称(采购)、客户uu(销售)、客户名称(销售)、
+	 *            物料编号、 物料名称、物料规格
+	 * @param tableName
+	 *            不为空,单据类型
+	 * @param pageParams
+	 *            可为空,翻页信息
+	 * @param status
+	 *            可为空,状态码
+	 * @return 单据id
+	 * @throws SearchException
+	 */
+	public SPage<Long> searchIds(String keyword, Table_name tableName, PageParams pageParams, Short status)
+			throws SearchException;
+}

+ 51 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/controller/IndexController.java

@@ -0,0 +1,51 @@
+package com.uas.search.console.b2b.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.uas.search.console.b2b.jms.AQListener;
+import com.uas.search.console.b2b.service.IndexService;
+
+/**
+ * 索引创建相关请求
+ * 
+ * @author sunyj
+ * @since 2016年8月5日 上午11:42:54
+ */
+@Controller
+@RequestMapping("/index")
+public class IndexController {
+
+	@Autowired
+	private IndexService indexService;
+
+	@Autowired
+	private AQListener aqListener;
+
+	@RequestMapping("/create")
+	@ResponseBody
+	public String initIndexes() {
+		return "Indexes created success in " + indexService.createIndexs() + " ms.";
+	}
+
+	@RequestMapping("/listen/start")
+	@ResponseBody
+	public String startListen() {
+		return aqListener.start();
+	}
+
+	@RequestMapping("/listen/stop")
+	@ResponseBody
+	public String stopListen() {
+		return aqListener.stop();
+	}
+
+	@RequestMapping("/maintain")
+	@ResponseBody
+	public String maintainIndexes(String tableName, String ids) {
+		return indexService.maintainIndexes(tableName, ids);
+	}
+
+}

+ 45 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/controller/SearchController.java

@@ -0,0 +1,45 @@
+package com.uas.search.console.b2b.controller;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Controller;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.ResponseBody;
+
+import com.uas.search.b2b.model.PageParams;
+import com.uas.search.b2b.model.SPage;
+import com.uas.search.b2b.service.SearchService;
+import com.uas.search.b2b.service.SearchService.Table_name;
+
+/**
+ * 搜索请求
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 下午4:33:32
+ */
+// 只用于测试
+@Controller
+@RequestMapping("/search")
+public class SearchController {
+
+	@Autowired
+	private SearchService searchService;
+
+	@RequestMapping("")
+	@ResponseBody
+	public SPage<Long> getKind(String keyword, String tableName, Integer page, Integer size, Short status) {
+		Table_name tbName = null;
+		if (!StringUtils.isEmpty(tableName)) {
+			tbName = Table_name.valueOf(tableName);
+		}
+		PageParams pageParams = new PageParams();
+		if (page != null) {
+			pageParams.setPage(page);
+		}
+		if (size != null) {
+			pageParams.setSize(size);
+		}
+		return searchService.searchIds(keyword, tbName, pageParams, status);
+	}
+
+}

+ 110 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/core/advice/ExceptionHandlerAdvice.java

@@ -0,0 +1,110 @@
+/*CopyRright (c)2014: <www.usoftchina.com>
+ */
+package com.uas.search.console.b2b.core.advice;
+
+import java.sql.SQLRecoverableException;
+
+import org.apache.log4j.Logger;
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.ui.ModelMap;
+import org.springframework.util.StringUtils;
+import org.springframework.web.bind.annotation.ControllerAdvice;
+import org.springframework.web.bind.annotation.ExceptionHandler;
+
+import com.uas.search.b2b.exception.SearchException;
+
+/**
+ * <p>
+ * 基于Application的异常处理,以AOP的形式注册到SpringMVC的处理链
+ * </p>
+ * <p>
+ * 正常的业务流程,只需抛出对应的异常和相关的信息
+ * </P>
+ * <p>
+ * 不同的错误,对应不同的方法来处理
+ * </p>
+ * <p>
+ * 可以用不同的HttpStatus来表示具体的异常类型
+ * </p>
+ * <p>
+ * 客户端可以基于对应的HttpStatus Code做出最有利于自己的处理
+ * </p>
+ * 
+ * @author yingp
+ * 
+ */
+@ControllerAdvice
+public class ExceptionHandlerAdvice {
+
+	private final static Logger logger = Logger.getLogger(ExceptionHandlerAdvice.class);
+
+	/**
+	 * 处理运行时抛出异常
+	 * 
+	 * @param ex
+	 * @return
+	 */
+	@ExceptionHandler(RuntimeException.class)
+	public ResponseEntity<String> handleUnexpectedServerError(RuntimeException ex) {
+		logger.error("RuntimeException", ex);
+		HttpHeaders headers = new HttpHeaders();
+		headers.add("Content-Type", "application/text; charset=utf-8");
+		ex.printStackTrace();
+		return new ResponseEntity<String>("\u7CFB\u7EDF\u9519\u8BEF", headers, HttpStatus.INTERNAL_SERVER_ERROR);
+	}
+
+	/**
+	 * 处理连接池的连接失效抛出异常
+	 * 
+	 * @see SQLRecoverableException
+	 * @param ex
+	 * @return
+	 */
+	@ExceptionHandler(SQLRecoverableException.class)
+	public ResponseEntity<String> handleSQLRecoverableException(SQLRecoverableException ex) {
+		logger.error("SQLRecoverableException", ex);
+		HttpHeaders headers = new HttpHeaders();
+		headers.add("Content-Type", "application/text; charset=utf-8");
+		return new ResponseEntity<String>("\u7CFB\u7EDF\u9519\u8BEF", headers, HttpStatus.INTERNAL_SERVER_ERROR);
+	}
+
+	/**
+	 * 处理参数错误抛出异常
+	 * 
+	 * @see IllegalArgumentException
+	 * @param ex
+	 * @return
+	 */
+	@ExceptionHandler(IllegalArgumentException.class)
+	public ResponseEntity<String> handleIllegalArgumentException(IllegalArgumentException ex) {
+		logger.error("IllegalArgumentException", ex);
+		HttpHeaders headers = new HttpHeaders();
+		headers.add("Content-Type", "application/text; charset=utf-8");
+		return new ResponseEntity<String>("\u53C2\u6570\u9519\u8BEF", headers, HttpStatus.INTERNAL_SERVER_ERROR);
+	}
+
+	/**
+	 * 处理已捕获异常,明确传达给客户端错误码、错误信息
+	 * 
+	 * @see SearchException
+	 * @param ex
+	 * @return
+	 */
+	@ExceptionHandler(SearchException.class)
+	public ResponseEntity<ModelMap> handleSearchException(SearchException ex) {
+		if (!StringUtils.isEmpty(ex.getDetailedMessage())) {
+			logger.error(ex.getDetailedMessage());
+		} else {
+			logger.error(ex.getMessage());
+		}
+		HttpHeaders headers = new HttpHeaders();
+		headers.add("Content-Type", "application/json; charset=utf-8");
+		ModelMap map = new ModelMap();
+		map.put("success", false);
+		map.put("message", ex.getMessage());
+		return new ResponseEntity<ModelMap>(map, headers, HttpStatus.BAD_REQUEST);
+	}
+
+}

+ 17 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/dao/MakeOrderSimpleInfoDao.java

@@ -0,0 +1,17 @@
+package com.uas.search.console.b2b.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import com.uas.search.console.b2b.model.MakeOrderSimpleInfo;
+
+/**
+ * @author sunyj
+ * @since 2016年11月9日 下午1:46:40
+ */
+@Repository
+public interface MakeOrderSimpleInfoDao
+		extends JpaSpecificationExecutor<MakeOrderSimpleInfo>, JpaRepository<MakeOrderSimpleInfo, Long> {
+	public MakeOrderSimpleInfo findById(Long id);
+}

+ 17 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/dao/PurchaseOrderSimpleInfoDao.java

@@ -0,0 +1,17 @@
+package com.uas.search.console.b2b.dao;
+
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+import com.uas.search.console.b2b.model.PurchaseOrderSimpleInfo;
+
+/**
+ * @author sunyj
+ * @since 2016年11月9日 下午1:46:40
+ */
+@Repository
+public interface PurchaseOrderSimpleInfoDao
+		extends JpaSpecificationExecutor<PurchaseOrderSimpleInfo>, JpaRepository<PurchaseOrderSimpleInfo, Long> {
+	public PurchaseOrderSimpleInfo findById(Long id);
+}

+ 190 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/jms/AQListener.java

@@ -0,0 +1,190 @@
+package com.uas.search.console.b2b.jms;
+
+import java.sql.SQLException;
+import java.util.Properties;
+
+import javax.jms.JMSException;
+import javax.jms.Message;
+import javax.jms.MessageConsumer;
+import javax.jms.MessageListener;
+import javax.jms.Queue;
+import javax.jms.QueueConnection;
+import javax.jms.QueueConnectionFactory;
+import javax.jms.Session;
+
+import org.apache.commons.dbcp.BasicDataSource;
+import org.apache.log4j.Logger;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+
+import com.alibaba.fastjson.JSONException;
+import com.uas.search.console.b2b.core.util.ContextUtils;
+import com.uas.search.console.b2b.model.ParsedQueueMessage;
+import com.uas.search.console.b2b.service.IndexService;
+import com.uas.search.console.b2b.util.SearchConstants;
+
+import oracle.jms.AQjmsAdtMessage;
+import oracle.jms.AQjmsFactory;
+import oracle.jms.AQjmsSession;
+
+/**
+ * 对数据库的消息队列进行实时监听
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 上午11:29:33
+ */
+@Service
+public class AQListener {
+
+	@Autowired
+	private IndexService indexService;
+
+	@Autowired
+	private QueueMessageParser queueMessageParser;
+
+	// 消息队列的消费者
+	private MessageConsumer consumer;
+
+	Logger logger = Logger.getLogger(getClass());
+
+	/**
+	 * 开启实时更新索引
+	 * 
+	 * @return 开启成功与否的提示信息
+	 */
+	public String start() {
+		String message = "";
+		if (isRunning()) {
+			message = "已存在运行的索引实时更新服务";
+			logger.warn(message);
+			return message;
+		}
+
+		BasicDataSource dataSource = ContextUtils.getApplicationContext().getBean("dataSource",
+				org.apache.commons.dbcp.BasicDataSource.class);
+
+		try {
+			QueueConnectionFactory queueConnectionFactory = AQjmsFactory.getQueueConnectionFactory(dataSource.getUrl(),
+					new Properties());
+			QueueConnection connection = queueConnectionFactory.createQueueConnection(dataSource.getUsername(),
+					dataSource.getPassword());
+			AQjmsSession session = (AQjmsSession) connection.createSession(false, Session.AUTO_ACKNOWLEDGE);
+			Queue queue = session.getQueue(dataSource.getUsername(), SearchConstants.LUCENE_QUEUE_NAME);
+			consumer = session.createConsumer(queue, null, QueueMessageTypeFactory.getFactory(), null, false);
+
+			// 添加监听器,队列中一旦有消息入队,就会接受该消息(并不是真的实时,一般会有10秒以内的延迟)
+			consumer.setMessageListener(new MessageListener() {
+				@Override
+				public void onMessage(Message message) {
+					try {
+						// 等待30秒,为了等待数据表变动的事务提交
+						Thread.sleep(30000);
+					} catch (InterruptedException e1) {
+						e1.printStackTrace();
+					}
+					AQjmsAdtMessage adtMessage = (AQjmsAdtMessage) message;
+					try {
+						QueueMessageTypeFactory payload = (QueueMessageTypeFactory) adtMessage.getAdtPayload();
+						// 对出队的消息进行解析、处理
+						process(payload.getMessage());
+					} catch (JMSException e) {
+						e.printStackTrace();
+					} catch (SQLException e) {
+						e.printStackTrace();
+					}
+				}
+			});
+			connection.start();
+			message = "索引实时更新服务成功开启";
+			logger.info(message);
+		} catch (JMSException e) {
+			message = "索引实时更新服务开启失败";
+			logger.error(message);
+			e.printStackTrace();
+			try {
+				consumer.close();
+			} catch (JMSException e1) {
+				e1.printStackTrace();
+			}
+			consumer = null;
+		}
+		return message;
+	}
+
+	/**
+	 * 关闭实时更新索引服务
+	 * 
+	 * @return 关闭成功与否的提示信息
+	 */
+	public String stop() {
+		String message = "";
+		if (!isRunning()) {
+			message = "索引实时更新服务未开启或已关闭";
+			logger.warn(message);
+		} else {
+			try {
+				consumer.close();
+				message = "索引实时更新服务成功关闭";
+				logger.info(message);
+			} catch (JMSException e) {
+				message = "索引实时更新服务关闭失败";
+				logger.error(message);
+				e.printStackTrace();
+			}
+			consumer = null;
+		}
+		return message;
+	}
+
+	/**
+	 * @return 索引实时更新服务是否正在运行
+	 */
+	public boolean isRunning() {
+		return consumer != null;
+	}
+
+	/**
+	 * 对得到的队列消息进行解析,之后根据解析出来的对象,对lucene索引进行添加、更新或删除操作
+	 * 
+	 * @param message
+	 */
+	private void process(String message) {
+		ParsedQueueMessage parsedQueueMessage = null;
+		logger.info(message);
+		try {
+			parsedQueueMessage = queueMessageParser.parse(message);
+		} catch (JSONException e) {
+			e.printStackTrace();
+		}
+
+		if (parsedQueueMessage == null) {
+			logger.error("message parsing failed!");
+			return;
+		}
+
+		Object[] objects = parsedQueueMessage.getObjects();
+		if (objects == null) {
+			return;
+		}
+		// 新增索引
+		if (parsedQueueMessage.isInsert()) {
+			for (Object object : objects) {
+				indexService.save(object);
+			}
+		}
+		// 更新索引
+		else if (parsedQueueMessage.isUpdate()) {
+			for (Object object : objects) {
+				indexService.update(object);
+			}
+		}
+		// 删除索引
+		else if (parsedQueueMessage.isDelete()) {
+			for (Object object : objects) {
+				indexService.delete(object);
+			}
+		} else {
+			logger.error("message parsing failed!");
+		}
+	}
+}

+ 83 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/jms/QueueMessageParser.java

@@ -0,0 +1,83 @@
+package com.uas.search.console.b2b.jms;
+
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import com.alibaba.fastjson.JSONException;
+import com.alibaba.fastjson.JSONObject;
+import com.uas.search.console.b2b.model.MakeOrderSimpleInfo;
+import com.uas.search.console.b2b.model.ParsedQueueMessage;
+import com.uas.search.console.b2b.model.PurchaseOrderSimpleInfo;
+
+/**
+ * 对得到的队列消息进行解析的工具
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 上午11:30:02
+ */
+@Service
+public class QueueMessageParser {
+
+	/**
+	 * 对得到的json消息进行解析
+	 * 
+	 * @param message
+	 * @return ParsedQueueMessage对象
+	 * @throws JSONException
+	 */
+	// {"method":"value1","table":"value2","param1":"value3","param2":"value4"}
+	public ParsedQueueMessage parse(String message) throws JSONException {
+		if (StringUtils.isEmpty(message) || message.equals("{}")) {
+			return null;
+		}
+		ParsedQueueMessage parsedQueueMessage = new ParsedQueueMessage();
+		JSONObject jsonObject = JSONObject.parseObject(message);
+
+		// 解析数据库表的更改类型
+		String method = jsonObject.getString("method");
+		if (method.equals("insert")) {
+			parsedQueueMessage.setMethodType(ParsedQueueMessage.INSERT);
+		}
+
+		else if (method.equals("update")) {
+			parsedQueueMessage.setMethodType(ParsedQueueMessage.UPDATE);
+		}
+
+		else if (method.equals("delete")) {
+			parsedQueueMessage.setMethodType(ParsedQueueMessage.DELETE);
+		} else {
+			return null;
+		}
+
+		// 解析哪个表有更改
+		Object[] objects = null;
+		String table = jsonObject.getString("table");
+		// TODO 其他表
+		if (table.equals(PurchaseOrderSimpleInfo.TABLE_NAME)) {
+			objects = parsePurchaseOrder(jsonObject);
+		} else if (table.equals(MakeOrderSimpleInfo.TABLE_NAME)) {
+			objects = parseMakeOrder(jsonObject);
+		} else {
+			return null;
+		}
+
+		parsedQueueMessage.setObjects(objects);
+		return parsedQueueMessage;
+	}
+
+	/**
+	 * 对PurchaseOrder进行解析
+	 * 
+	 * @param jsonObject
+	 * @return PurchaseOrder对象数组
+	 * @throws JSONException
+	 */
+	// {"method":"insert","table":"purc$orders","ids":[2400,1299]}
+	private PurchaseOrderSimpleInfo[] parsePurchaseOrder(JSONObject jsonObject) throws JSONException {
+		return null;
+	}
+
+	private MakeOrderSimpleInfo[] parseMakeOrder(JSONObject jsonObject) throws JSONException {
+		return null;
+	}
+}

+ 58 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/jms/QueueMessageTypeFactory.java

@@ -0,0 +1,58 @@
+package com.uas.search.console.b2b.jms;
+
+import java.sql.SQLException;
+
+import oracle.jdbc.OracleTypes;
+import oracle.jdbc.driver.OracleConnection;
+import oracle.jpub.runtime.MutableStruct;
+import oracle.sql.CustomDatum;
+import oracle.sql.CustomDatumFactory;
+import oracle.sql.Datum;
+import oracle.sql.STRUCT;
+
+/**
+ * 对数据库的数据格式进行转换
+ * 
+ * @author sunyj
+ * @since 2016年7月7日 下午8:49:44
+ */
+@SuppressWarnings("deprecation")
+public class QueueMessageTypeFactory implements CustomDatum, CustomDatumFactory {
+
+	public static final int SQL_TYPECODE = OracleTypes.STRUCT;
+
+	MutableStruct struct;
+
+	// 12表示字符串
+	static int[] sqlType = { 12 };
+	static CustomDatumFactory[] factory = new CustomDatumFactory[1];
+	static final QueueMessageTypeFactory messageFactory = new QueueMessageTypeFactory();
+
+	public QueueMessageTypeFactory() {
+		struct = new MutableStruct(new Object[1], sqlType, factory);
+	}
+
+	public static CustomDatumFactory getFactory() {
+		return messageFactory;
+	}
+
+	@Override
+	public CustomDatum create(Datum datum, int sqlType) throws SQLException {
+		if (datum == null) {
+			return null;
+		}
+		QueueMessageTypeFactory queueMessageType = new QueueMessageTypeFactory();
+		queueMessageType.struct = new MutableStruct((STRUCT) datum, QueueMessageTypeFactory.sqlType, factory);
+		return queueMessageType;
+	}
+
+	@Override
+	public Datum toDatum(OracleConnection connection) throws SQLException {
+		return struct.toDatum(connection, "QueueMessageTypeFactory");
+	}
+
+	public String getMessage() throws SQLException {
+		return (String) struct.getAttribute(0);
+	}
+
+}

+ 68 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/model/EnterpriseSimpleInfo.java

@@ -0,0 +1,68 @@
+/*CopyRright (c)2014: <www.usoftchina.com>
+ */
+package com.uas.search.console.b2b.model;
+
+import java.io.Serializable;
+import java.util.Objects;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ * 企业信息
+ * 
+ * @author sunyj
+ * @since 2016年10月14日 上午10:55:23
+ */
+@Entity
+@Table(name = "sec$enterprises")
+public class EnterpriseSimpleInfo implements Serializable {
+
+	/**
+	 * 序列号
+	 */
+	private static final long serialVersionUID = 1L;
+
+	@Id
+	@Column(name = "en_uu")
+	private Long uu;
+
+	/**
+	 * 公司名称
+	 */
+	@Column(name = "en_name")
+	private String enName;
+
+	public Long getUu() {
+		return uu;
+	}
+
+	public void setUu(Long uu) {
+		this.uu = uu;
+	}
+
+	public String getEnName() {
+		return enName;
+	}
+
+	public void setEnName(String enName) {
+		this.enName = enName;
+	}
+
+	public boolean equals(Object otherObject) {
+		if (this == otherObject || otherObject == null || getClass() != otherObject.getClass()
+				|| !(otherObject instanceof EnterpriseSimpleInfo)) {
+			return true;
+		}
+		EnterpriseSimpleInfo other = (EnterpriseSimpleInfo) otherObject;
+		return Objects.equals(uu, other.getUu()) && Objects.equals(enName, other.getEnName());
+	}
+
+	@Override
+	public String toString() {
+		return "EnterpriseSimpleInfo [uu=" + uu + ", enName=" + enName + "]";
+	}
+
+}

+ 132 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/model/MakeOrderSimpleInfo.java

@@ -0,0 +1,132 @@
+package com.uas.search.console.b2b.model;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+/**
+ * 平台里面,以供应商的角度来查看委外加工单
+ * 
+ * @author suntg
+ *
+ */
+@Entity
+@Table(name = MakeOrderSimpleInfo.TABLE_NAME)
+public class MakeOrderSimpleInfo {
+
+	/**
+	 * 表名
+	 */
+	public static final String TABLE_NAME = "MAKE$ORDERS";
+
+	/**
+	 * 各字段列名(也是存储索引时的field名)
+	 */
+	public static final String ID_FIELD = "ma_id";
+
+	public static final String CODE_FIELD = "ma_code";
+
+	public static final String STATUS_FIELD = "ma_status";
+
+	public static final String VEND_FIELD = "ma_venduu";
+
+	public static final String PRODUCT_FIELD = "ma_prid";
+
+	@Id
+	@Column(name = ID_FIELD)
+	private Long id;
+
+	/**
+	 * 委外加工单号
+	 */
+	@Column(name = CODE_FIELD)
+	private String code;
+
+	/**
+	 * 处理状态(已回复、未回复),全部回复后改为已回复
+	 */
+	@Column(name = STATUS_FIELD)
+	private Short status;
+
+	/**
+	 * 供应商
+	 */
+	@OneToOne
+	@JoinColumn(name = VEND_FIELD, insertable = false, updatable = false)
+	private EnterpriseSimpleInfo vend;
+
+	/**
+	 * 产品
+	 */
+	@OneToOne(cascade = { CascadeType.REFRESH })
+	@JoinColumn(name = PRODUCT_FIELD, insertable = false, updatable = false)
+	private ProductSimpleInfo product;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public EnterpriseSimpleInfo getVend() {
+		return vend;
+	}
+
+	public void setVend(EnterpriseSimpleInfo vend) {
+		this.vend = vend;
+	}
+
+	public ProductSimpleInfo getProduct() {
+		return product;
+	}
+
+	public void setProduct(ProductSimpleInfo product) {
+		this.product = product;
+	}
+
+	public Short getStatus() {
+		return status;
+	}
+
+	public void setStatus(Short status) {
+		this.status = status;
+	}
+
+	/**
+	 * 获取除了status外的所有字段的列名(field名)
+	 * 
+	 * @return
+	 */
+	public static List<String> getAllExceptStatusFields() {
+		List<String> fields = new ArrayList<>();
+		fields.add(ID_FIELD);
+		fields.add(CODE_FIELD);
+		fields.add(VEND_FIELD);
+		fields.add(PRODUCT_FIELD);
+		return fields;
+	}
+
+	@Override
+	public String toString() {
+		return "MakeOrderSimpleInfo [id=" + id + ", code=" + code + ", vend=" + vend + ", product=" + product
+				+ ", status=" + status + "]";
+	}
+
+}

+ 86 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/model/ParsedQueueMessage.java

@@ -0,0 +1,86 @@
+package com.uas.search.console.b2b.model;
+
+import java.util.Arrays;
+
+/**
+ * 对数据库队列里的消息进行解析后所得到的数据
+ * 
+ * @author sunyj
+ * @since 2016年7月7日 下午8:50:13
+ */
+public class ParsedQueueMessage {
+
+	/**
+	 * 数据库中表的改动为insert类型
+	 */
+	public static final int INSERT = 1;
+
+	/**
+	 * 改动为update类型
+	 */
+	public static final int UPDATE = 2;
+
+	/**
+	 * 改动为delete类型
+	 */
+	public static final int DELETE = 3;
+
+	/**
+	 * 表改动后,解析消息时,更改的类型
+	 */
+	private int methodType;
+
+	/**
+	 * 存放解析出来的对象:kind、brand或component、order、purchase等对象
+	 */
+	private Object[] objects;
+
+	/**
+	 * 是否为insert类型
+	 * 
+	 * @return
+	 */
+	public boolean isInsert() {
+		return methodType == INSERT;
+	}
+
+	/**
+	 * 是否为update类型
+	 * 
+	 * @return
+	 */
+	public boolean isUpdate() {
+		return methodType == UPDATE;
+	}
+
+	/**
+	 * 是否为delete类型
+	 * 
+	 * @return
+	 */
+	public boolean isDelete() {
+		return methodType == DELETE;
+	}
+
+	public int getMethodType() {
+		return methodType;
+	}
+
+	public void setMethodType(int methodType) {
+		this.methodType = methodType;
+	}
+
+	public Object[] getObjects() {
+		return objects;
+	}
+
+	public void setObjects(Object[] objects) {
+		this.objects = objects;
+	}
+
+	@Override
+	public String toString() {
+		return "ParsedQueueMessage [methodType=" + methodType + ", object=" + Arrays.toString(objects) + "]";
+	}
+
+}

+ 69 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/model/ProductSimpleInfo.java

@@ -0,0 +1,69 @@
+package com.uas.search.console.b2b.model;
+
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.Table;
+
+/**
+ * 商品信息
+ * 
+ * @author yingp
+ *
+ */
+@Entity
+@Table(name = "products")
+public class ProductSimpleInfo {
+
+	@Id
+	@Column(name = "pr_id")
+	private Long id;
+
+	/**
+	 * 商品信息标题
+	 */
+	@Column(name = "pr_title")
+	private String title;
+
+	/**
+	 * 产品编号
+	 */
+	@Column(name = "pr_code")
+	private String code;
+
+	/**
+	 * 产品规格
+	 */
+	@Column(name = "pr_spec")
+	private String spec;
+
+	public String getTitle() {
+		return title;
+	}
+
+	public void setTitle(String title) {
+		this.title = title;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public String getSpec() {
+		return spec;
+	}
+
+	public void setSpec(String spec) {
+		this.spec = spec;
+	}
+
+	@Override
+	public String toString() {
+		return "ProductSimpleInfo [title=" + title + ", code=" + code + ", spec=" + spec + "]";
+	}
+
+}

+ 67 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/model/PurchaseOrderItemSimpleInfo.java

@@ -0,0 +1,67 @@
+package com.uas.search.console.b2b.model;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToOne;
+import javax.persistence.Table;
+
+/**
+ * 采购单明细
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 下午2:51:26
+ */
+@Entity
+@Table(name = "purc$orderitems")
+public class PurchaseOrderItemSimpleInfo {
+
+	@Id
+	@Column(name = "pd_id")
+	private Long id;
+
+	/**
+	 * 序号
+	 */
+	@Column(name = "pd_number")
+	private Short number;
+
+	/**
+	 * 产品
+	 */
+	@OneToOne(cascade = { CascadeType.REFRESH })
+	@JoinColumn(name = "pd_prid", insertable = false, updatable = false)
+	private ProductSimpleInfo product;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public Short getNumber() {
+		return number;
+	}
+
+	public void setNumber(Short number) {
+		this.number = number;
+	}
+
+	public ProductSimpleInfo getProduct() {
+		return product;
+	}
+
+	public void setProduct(ProductSimpleInfo product) {
+		this.product = product;
+	}
+
+	@Override
+	public String toString() {
+		return "PurchaseOrderItemSimpleInfo [id=" + id + ", number=" + number + ", product=" + product + "]";
+	}
+
+}

+ 137 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/model/PurchaseOrderSimpleInfo.java

@@ -0,0 +1,137 @@
+package com.uas.search.console.b2b.model;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import javax.persistence.CascadeType;
+import javax.persistence.Column;
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.Id;
+import javax.persistence.JoinColumn;
+import javax.persistence.OneToMany;
+import javax.persistence.OneToOne;
+import javax.persistence.OrderBy;
+import javax.persistence.Table;
+
+/**
+ * 平台里面,以供应商的角度来查看采购订单
+ * 
+ * @author yingp
+ *
+ */
+@Entity
+@Table(name = PurchaseOrderSimpleInfo.TABLE_NAME)
+public class PurchaseOrderSimpleInfo {
+
+	/**
+	 * 表名
+	 */
+	public static final String TABLE_NAME = "PURC$ORDERS";
+
+	/**
+	 * 各字段列名(也是存储索引时的field名)
+	 */
+	public static final String ID_FIELD = "pu_id";
+
+	public static final String CODE_FIELD = "pu_code";
+
+	public static final String STATUS_FIELD = "pu_status";
+
+	public static final String VEND_FIELD = "pu_venduu";
+
+	public static final String ITEMS_FIELD = "pd_puid";
+
+	@Id
+	@Column(name = ID_FIELD)
+	private Long id;
+
+	/**
+	 * 采购单号
+	 */
+	@Column(name = CODE_FIELD)
+	private String code;
+
+	/**
+	 * 处理状态(已回复、未回复),全部回复后改为已回复
+	 */
+	@Column(name = STATUS_FIELD)
+	private Short status;
+
+	/**
+	 * 供应商
+	 */
+	@OneToOne
+	@JoinColumn(name = VEND_FIELD, insertable = false, updatable = false)
+	private EnterpriseSimpleInfo vend;
+
+	/**
+	 * 采购单明细
+	 */
+	@OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
+	@JoinColumn(name = ITEMS_FIELD, updatable = false, insertable = false)
+	@OrderBy("number")
+	private Set<PurchaseOrderItemSimpleInfo> orderItems;
+
+	public Long getId() {
+		return id;
+	}
+
+	public void setId(Long id) {
+		this.id = id;
+	}
+
+	public String getCode() {
+		return code;
+	}
+
+	public void setCode(String code) {
+		this.code = code;
+	}
+
+	public EnterpriseSimpleInfo getVend() {
+		return vend;
+	}
+
+	public void setVend(EnterpriseSimpleInfo vend) {
+		this.vend = vend;
+	}
+
+	public Short getStatus() {
+		return status;
+	}
+
+	public void setStatus(Short status) {
+		this.status = status;
+	}
+
+	public Set<PurchaseOrderItemSimpleInfo> getOrderItems() {
+		return orderItems;
+	}
+
+	public void setOrderItems(Set<PurchaseOrderItemSimpleInfo> orderItems) {
+		this.orderItems = orderItems;
+	}
+
+	/**
+	 * 获取除了status外的所有字段的列名(field名)
+	 * 
+	 * @return
+	 */
+	public static List<String> getAllExceptStatusFields() {
+		List<String> fields = new ArrayList<>();
+		fields.add(ID_FIELD);
+		fields.add(CODE_FIELD);
+		fields.add(VEND_FIELD);
+		fields.add(ITEMS_FIELD);
+		return fields;
+	}
+
+	@Override
+	public String toString() {
+		return "PurchaseOrderSimpleInfo [id=" + id + ", code=" + code + ", vend=" + vend + ", status=" + status
+				+ ", orderItems=" + orderItems + "]";
+	}
+
+}

+ 52 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/service/IndexService.java

@@ -0,0 +1,52 @@
+package com.uas.search.console.b2b.service;
+
+/**
+ * 索引处理
+ * 
+ * @author sunyj
+ * @since 2016年8月5日 下午2:33:58
+ */
+public interface IndexService {
+
+	/**
+	 * 初始化时,从数据库中得到全部类目、品牌和器件对象,写进索引中
+	 * 
+	 * @return 创建的索引花费总时间 ms
+	 */
+	public Long createIndexs();
+
+	/**
+	 * 将新对象添加在lucene索引中
+	 * 
+	 * @param obj
+	 *            类目、品牌或器件对象
+	 */
+	public void save(Object obj);
+
+	/**
+	 * 根据新对象对lucene索引进行更新
+	 * 
+	 * @param obj
+	 *            类目、品牌或器件对象
+	 */
+	public void update(Object obj);
+
+	/**
+	 * 将对象从lucene索引中删除
+	 * 
+	 * @param obj
+	 *            类目、品牌或器件对象
+	 */
+	public void delete(Object obj);
+
+	/**
+	 * 用于维护出问题的索引
+	 * 
+	 * @param tableName
+	 *            需维护的表名(如product$kind)
+	 * @param ids
+	 *            需维护的id,多个id以英文逗号隔开,如:321,988
+	 * @return 维护的结果
+	 */
+	public String maintainIndexes(String tableName, String ids);
+}

+ 289 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/service/impl/IndexServiceImpl.java

@@ -0,0 +1,289 @@
+package com.uas.search.console.b2b.service.impl;
+
+import java.io.IOException;
+import java.nio.file.Paths;
+import java.util.ArrayList;
+import java.util.Date;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.store.FSDirectory;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Service;
+import org.springframework.util.StringUtils;
+
+import com.uas.search.b2b.exception.SearchException;
+import com.uas.search.console.b2b.dao.MakeOrderSimpleInfoDao;
+import com.uas.search.console.b2b.dao.PurchaseOrderSimpleInfoDao;
+import com.uas.search.console.b2b.jms.AQListener;
+import com.uas.search.console.b2b.model.MakeOrderSimpleInfo;
+import com.uas.search.console.b2b.model.PurchaseOrderSimpleInfo;
+import com.uas.search.console.b2b.service.IndexService;
+import com.uas.search.console.b2b.support.IndexWriterManager;
+import com.uas.search.console.b2b.util.ObjectToDocumentUtils;
+import com.uas.search.console.b2b.util.SearchConstants;
+
+/**
+ * 创建索引等
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 上午11:20:51
+ */
+@Service
+public class IndexServiceImpl implements IndexService {
+
+	@Autowired
+	private PurchaseOrderSimpleInfoDao purchaseOrderDao;
+
+	@Autowired
+	private MakeOrderSimpleInfoDao makeOrderDao;
+
+	private IndexWriter indexWriter;
+
+	private static IndexWriterManager indexWriterManager;
+
+	private FSDirectory directory;
+
+	@Autowired
+	private AQListener aqListener;
+
+	/**
+	 * 是否正在创建索引
+	 */
+	private boolean creatingIndex = false;
+
+	Logger logger = Logger.getLogger(getClass());
+
+	public IndexServiceImpl() {
+		try {
+			directory = FSDirectory.open(Paths.get(SearchConstants.INDEX_DIR));
+			indexWriterManager = new IndexWriterManager(directory);
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	@Override
+	public Long createIndexs() {
+		if (creatingIndex) {
+			throw new SearchException("已存在线程在创建索引,不可重复请求");
+		}
+		creatingIndex = true;
+		// 如果索引实时更新处于开启状态,需要暂时关闭(以免两者同时操作索引出现问题)
+		if (aqListener.isRunning()) {
+			logger.info("索引实时更新服务正在运行,尝试关闭索引实时更新服务...");
+			aqListener.stop();
+		}
+
+		try {
+			indexWriter = indexWriterManager.get();
+			logger.info("正在清理旧索引...");
+			indexWriter.deleteAll();
+			indexWriter.commit();
+			logger.info("旧索引清理完毕");
+			Long startTime = new Date().getTime();
+
+			Long purchaseOrderSize = createPurchaseOrderIndexs();
+			Long purchaseOrderTime = new Date().getTime();
+			logger.info(
+					"创建PurchaseOrder索引: " + purchaseOrderSize + "条,耗时 " + (purchaseOrderTime - startTime) + " ms\n");
+
+			Long makeOrderSize = createMakeOrderIndexs();
+			Long makeOrderTime = new Date().getTime();
+			logger.info("创建MakeOrder索引: " + makeOrderSize + "条,耗时 " + (makeOrderTime - purchaseOrderTime) + " ms\n");
+
+			// TODO 其他表
+
+			logger.info("索引创建成功, 共用时间 " + (makeOrderTime - startTime) + " ms");
+			return makeOrderTime - startTime;
+		} catch (IOException e) {
+			e.printStackTrace();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		} finally {
+			// aqListener.start();
+			creatingIndex = false;
+			indexWriterManager.release();
+		}
+		return null;
+	}
+
+	/**
+	 * 创建PurchaseOrder索引
+	 * 
+	 * @return 写入的PurchaseOrder索引数
+	 * @throws IOException
+	 */
+	private Long createPurchaseOrderIndexs() throws IOException {
+		logger.info("正在创建PurchaseOrder索引...");
+		List<PurchaseOrderSimpleInfo> purchaseOrders = purchaseOrderDao.findAll();
+		logger.info("发现数据:" + purchaseOrders.size() + "条");
+		return createIndexesWithObjects(purchaseOrders.toArray());
+	}
+
+	/**
+	 * 创建MakeOrder索引
+	 * 
+	 * @return 写入的MakeOrder索引数
+	 * @throws IOException
+	 */
+	private Long createMakeOrderIndexs() throws IOException {
+		logger.info("正在创建MakeOrder索引...");
+		List<MakeOrderSimpleInfo> makeOrders = makeOrderDao.findAll();
+		logger.info("发现数据:" + makeOrders.size() + "条");
+		return createIndexesWithObjects(makeOrders.toArray());
+	}
+
+	/**
+	 * 利用对象数组创建索引
+	 * 
+	 * @param objects
+	 *            对象数组,可为PurchaseOrderSimpleInfo、MakeOrderSimpleInfo.....
+	 * @return 对象数组的数量
+	 */
+	private Long createIndexesWithObjects(Object[] objects) {
+		if (objects == null || objects.length < 1)
+			return 0L;
+
+		long count = 0;
+		try {
+			for (Object object : objects) {
+				Document document = ObjectToDocumentUtils.toDocument(object);
+				if (document != null) {
+					indexWriter.addDocument(document);
+					count++;
+				}
+			}
+			indexWriter.commit();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return count;
+	}
+
+	@Override
+	public void save(Object obj) {
+		if (obj == null) {
+			return;
+		}
+
+		Document document = ObjectToDocumentUtils.toDocument(obj);
+		if (document != null) {
+			try {
+				indexWriter = indexWriterManager.get();
+				indexWriter.addDocument(document);
+				indexWriter.commit();
+				logger.info("Saved... " + obj + "\n");
+			} catch (IOException | InterruptedException e) {
+				e.printStackTrace();
+			} finally {
+				indexWriterManager.release();
+			}
+		} else {
+			logger.info("对象转为Document时为null:" + obj);
+		}
+	}
+
+	@Override
+	public void update(Object obj) {
+		if (obj == null) {
+			return;
+		}
+		Document document = ObjectToDocumentUtils.toDocument(obj);
+		if (document != null) {
+			try {
+				indexWriter = indexWriterManager.get();
+				if (obj instanceof PurchaseOrderSimpleInfo) {
+					indexWriter.updateDocument(new Term(PurchaseOrderSimpleInfo.ID_FIELD,
+							String.valueOf(((PurchaseOrderSimpleInfo) obj).getId())), document);
+				} else if (obj instanceof MakeOrderSimpleInfo) {
+					indexWriter.updateDocument(
+							new Term(MakeOrderSimpleInfo.ID_FIELD, String.valueOf(((MakeOrderSimpleInfo) obj).getId())),
+							document);
+				}
+				// TODO 其他表
+				else {
+					logger.error("Message parsing failed!");
+				}
+				indexWriter.commit();
+				logger.info("Updated... " + obj + "\n");
+			} catch (IOException | InterruptedException e) {
+				e.printStackTrace();
+			} finally {
+				indexWriterManager.release();
+			}
+		} else {
+			logger.info("对象转为Document时为null:" + obj);
+		}
+	}
+
+	@Override
+	public void delete(Object obj) {
+		if (obj == null) {
+			return;
+		}
+		try {
+			indexWriter = indexWriterManager.get();
+			if (obj instanceof PurchaseOrderSimpleInfo) {
+				indexWriter.deleteDocuments(new Term(PurchaseOrderSimpleInfo.ID_FIELD,
+						String.valueOf(((PurchaseOrderSimpleInfo) obj).getId())));
+			} else if (obj instanceof MakeOrderSimpleInfo) {
+				indexWriter.deleteDocuments(
+						new Term(MakeOrderSimpleInfo.ID_FIELD, String.valueOf(((MakeOrderSimpleInfo) obj).getId())));
+			}
+			// TODO 其他表
+			else {
+				logger.error("Message parsing failed!");
+			}
+			indexWriter.commit();
+		} catch (IOException e) {
+			e.printStackTrace();
+		} catch (InterruptedException e) {
+			e.printStackTrace();
+		} finally {
+			indexWriterManager.release();
+		}
+		logger.info("Deleted... " + obj + "\n");
+	}
+
+	@Override
+	public String maintainIndexes(String tableName, String ids) {
+		if (StringUtils.isEmpty(tableName)) {
+			throw new SearchException("需指定要维护的表名,如:" + PurchaseOrderSimpleInfo.TABLE_NAME);
+		}
+		if (StringUtils.isEmpty(ids)) {
+			throw new SearchException("需指定要维护的ids,多个id以英文逗号隔开,如:321,988");
+		}
+
+		String[] strs = ids.split(",");
+		List<Long> idList = new ArrayList<>();
+		for (String str : strs) {
+			idList.add(Long.parseLong(str));
+		}
+		if (tableName.equalsIgnoreCase(PurchaseOrderSimpleInfo.TABLE_NAME)) {
+			update(purchaseOrderDao.findAll(idList));
+		} else if (tableName.equalsIgnoreCase(MakeOrderSimpleInfo.TABLE_NAME)) {
+			update(makeOrderDao.findAll(idList));
+		}
+		// TODO 其他表
+		else {
+			throw new SearchException("暂时不支持该表的索引进行维护:" + tableName);
+		}
+		return "维护成功";
+	}
+
+	/**
+	 * 根据对象列表,对索引进行更新
+	 * 
+	 * @param list
+	 */
+	private <T> void update(List<T> list) {
+		for (T element : list) {
+			update(element);
+		}
+	}
+
+}

+ 138 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/service/impl/SearchServiceImpl.java

@@ -0,0 +1,138 @@
+package com.uas.search.console.b2b.service.impl;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.log4j.Logger;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.springframework.stereotype.Service;
+
+import com.uas.search.b2b.exception.SearchException;
+import com.uas.search.b2b.model.PageParams;
+import com.uas.search.b2b.model.SPage;
+import com.uas.search.b2b.service.SearchService;
+import com.uas.search.console.b2b.util.SearchUtils;
+
+/**
+ * 搜索实现
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 下午4:47:09
+ */
+@Service
+public class SearchServiceImpl implements SearchService {
+
+	/**
+	 * 默认的页码
+	 */
+	private static final int PAGE_INDEX = 1;
+
+	/**
+	 * 默认每页的大小
+	 */
+	private static final int PAGE_SIZE = 20;
+
+	private Logger logger = Logger.getLogger(getClass());
+
+	@Override
+	public SPage<Long> searchIds(String keyword, Table_name tableName, PageParams pageParams, Short status)
+			throws SearchException {
+		if (SearchUtils.isKeywordInvalid(keyword)) {
+			throw new SearchException("搜索关键词无效:" + keyword);
+		}
+		IndexSearcher indexSearcher = SearchUtils.getIndexSearcher();
+		// 获取该表keyword可以搜索的域
+		List<String> keywordFields = SearchUtils.getKeywordFields(tableName);
+
+		SPage<Long> sPage = new SPage<>();
+		BooleanQuery booleanQuery = new BooleanQuery();
+
+		// 关键词带空格,进行与操作
+		String[] strs = keyword.split(" ");
+		for (String str : strs) {
+			// keyword可能是哪些域,域之间进行或操作
+			BooleanQuery booleanQuery2 = new BooleanQuery();
+			for (String keywordField : keywordFields) {
+				booleanQuery2.add(SearchUtils.getBooleanQuery(keywordField, str), BooleanClause.Occur.SHOULD);
+			}
+			booleanQuery.add(booleanQuery2, BooleanClause.Occur.MUST);
+		}
+		// 可能需要对status状态进行过滤
+		if (status != null) {
+			booleanQuery.add(new TermQuery(new Term(SearchUtils.getStatusField(tableName), String.valueOf(status))),
+					BooleanClause.Occur.MUST);
+		}
+		logger.info(booleanQuery);
+
+		// 分页信息
+		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);
+			}
+		}
+
+		try {
+			TopDocs topDocs;
+			// 如果页码不为1
+			if (sPage.getPage() > 1) {
+				TopDocs previousTopDocs = indexSearcher.search(booleanQuery, (sPage.getPage() - 1) * sPage.getSize());
+				int totalHits = previousTopDocs.totalHits;
+				ScoreDoc[] previousScoreDocs = previousTopDocs.scoreDocs;
+				if ((sPage.getPage() - 1) * sPage.getSize() >= totalHits) {
+					throw new SearchException("页码过大:元素总数量为" + totalHits);
+				}
+				topDocs = indexSearcher.searchAfter(previousScoreDocs[previousScoreDocs.length - 1], booleanQuery,
+						sPage.getSize());
+			} else {
+				sPage.setFirst(true);
+				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);
+			}
+
+			// 获取单据的id
+			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+			List<Long> content = new ArrayList<>();
+			for (ScoreDoc scoreDoc : scoreDocs) {
+				Document document = indexSearcher.doc(scoreDoc.doc);
+				content.add(Long.valueOf(document.get(SearchUtils.getIdField(tableName))));
+			}
+			sPage.setContent(content);
+		} catch (NumberFormatException | IOException e) {
+			throw new SearchException(e).setDetailedMessage(e);
+		} finally {
+			SearchUtils.releaseIndexSearcher(indexSearcher);
+		}
+		return sPage;
+	}
+
+}

+ 19 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/support/ApplicationContextRegister.java

@@ -0,0 +1,19 @@
+package com.uas.search.console.b2b.support;
+
+import org.apache.log4j.Logger;
+import org.springframework.beans.BeansException;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.ApplicationContextAware;
+
+import com.uas.search.console.b2b.core.util.ContextUtils;
+
+public class ApplicationContextRegister implements ApplicationContextAware {
+
+	private static Logger logger = Logger.getLogger(ApplicationContextRegister.class);
+
+	public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
+		ContextUtils.setApplicationContext(applicationContext);
+		logger.debug("ApplicationContext registed");
+	}
+
+}

+ 163 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/support/IndexSearcherManager.java

@@ -0,0 +1,163 @@
+package com.uas.search.console.b2b.support;
+
+import java.io.File;
+import java.io.IOException;
+import java.nio.file.Paths;
+
+import org.apache.log4j.Logger;
+import org.apache.lucene.index.DirectoryReader;
+import org.apache.lucene.index.IndexReader;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.store.FSDirectory;
+import org.apache.lucene.store.NIOFSDirectory;
+
+import com.uas.search.console.b2b.util.SearchConstants;
+
+/**
+ * 将索引加入缓存,对IndexSearcher进行管理
+ * 
+ * @author sunyj
+ * @since 2016年7月7日 下午8:50:55
+ */
+public class IndexSearcherManager {
+
+	private IndexSearcher currentSearcher;
+
+	private FSDirectory directory;
+
+	private Logger logger = Logger.getLogger(IndexSearcherManager.class);
+
+	public IndexSearcherManager() {
+		try {
+			String os = System.getProperty("os.name");
+			// 本来NIOFSDirectory速度更快,但因为jre的bug,在Windows平台上,其性能很差
+			if (os.toLowerCase().startsWith("win")) {
+				directory = FSDirectory.open(Paths.get(SearchConstants.INDEX_DIR));
+			} else {
+				directory = NIOFSDirectory.open(Paths.get(SearchConstants.INDEX_DIR));
+			}
+			warm();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+	}
+
+	public IndexSearcherManager(FSDirectory dir) {
+		directory = dir;
+		warm();
+	}
+
+	/**
+	 * 对currentSearcher进行初始化操作
+	 * 
+	 * @throws IOException
+	 */
+	public synchronized void warm() {
+		File[] files = directory.getDirectory().toFile().listFiles();
+		// 不为空的话说明索引已成功加载
+		if (currentSearcher == null) {
+			// 路径不为空文件夹
+			if (files.length != 0) {
+				try {
+					currentSearcher = new IndexSearcher(DirectoryReader.open(directory));
+				} catch (IOException e) {
+					e.printStackTrace();
+				}
+			} else {
+				logger.error("索引文件不存在!");
+			}
+		}
+	}
+
+	/**
+	 * 内存中IndexReader(可能)更新,根据版本号确定是否更新
+	 * 
+	 * @throws InterruptedException
+	 * @throws IOException
+	 */
+	public void maybeReopen() {
+		startReopen();
+
+		try {
+			// 每次都要进行初始化处理,是为了防止加载索引时,索引为空,而后来索引不为空时,却不能够正确加载
+			warm();
+			if (currentSearcher == null) {
+				return;
+			}
+			IndexSearcher searcher = get();
+			Long currentVersion = ((DirectoryReader) searcher.getIndexReader()).getVersion();
+			// 版本号不一致(本地索引有更改),更新IndexReader
+			try {
+				if (DirectoryReader.open(directory).getVersion() != currentVersion) {
+					IndexReader newReader = DirectoryReader.open(directory);
+					IndexSearcher newSearcher = new IndexSearcher(newReader);
+					swapIndexSearcher(newSearcher);
+				}
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+			release(searcher);
+		} finally {
+			doneReopen();
+		}
+
+	}
+
+	private boolean reopening = false;
+
+	private synchronized void startReopen() {
+		while (reopening) {
+			try {
+				wait();
+			} catch (InterruptedException e) {
+				e.printStackTrace();
+			}
+		}
+		reopening = true;
+	}
+
+	private synchronized void doneReopen() {
+		reopening = false;
+		notifyAll();
+	}
+
+	/**
+	 * 得到IndexSearcher
+	 * 
+	 * @return
+	 */
+	public synchronized IndexSearcher get() {
+		if (currentSearcher != null) {
+			currentSearcher.getIndexReader().incRef();
+		}
+		return currentSearcher;
+	}
+
+	/**
+	 * 释放indexSearcher,将对indexReader的引用减1,为0时成为垃圾
+	 * 
+	 * @param indexSearcher
+	 * @throws IOException
+	 */
+	public synchronized void release(IndexSearcher indexSearcher) {
+		if (indexSearcher != null) {
+			try {
+				indexSearcher.getIndexReader().decRef();
+			} catch (IOException e) {
+				e.printStackTrace();
+			}
+		}
+	}
+
+	/**
+	 * 更新IndexSearcher
+	 * 
+	 * @param indexSearcher
+	 *            新IndexSearcher
+	 * @throws IOException
+	 */
+	private synchronized void swapIndexSearcher(IndexSearcher indexSearcher) {
+		release(currentSearcher);
+		currentSearcher = indexSearcher;
+	}
+}

+ 66 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/support/IndexWriterManager.java

@@ -0,0 +1,66 @@
+package com.uas.search.console.b2b.support;
+
+import java.io.IOException;
+
+import org.apache.lucene.index.IndexWriter;
+import org.apache.lucene.index.IndexWriterConfig;
+import org.apache.lucene.store.FSDirectory;
+
+import com.uas.search.console.b2b.util.SearchConstants;
+
+/**
+ * 对IndexWriter进行管理,防止同时有多个方法对索引进行修改,抛出LockObtainFailedException异常
+ * 
+ * @author sunyj
+ * @since 2016年7月7日 下午8:50:04
+ */
+public class IndexWriterManager {
+
+	private IndexWriter indexWriter;
+
+	private FSDirectory directory;
+
+	public IndexWriterManager(FSDirectory dir) {
+		directory = dir;
+	}
+
+	/**
+	 * 得到IndexWriter,用完后需调用release释放IndexWriter
+	 * 
+	 * @return
+	 * @throws IOException
+	 * @throws InterruptedException
+	 */
+	public synchronized IndexWriter get() throws IOException, InterruptedException {
+		startUsing();
+		if (indexWriter == null) {
+			IndexWriterConfig config = new IndexWriterConfig(SearchConstants.IK_ANALYZER);// 此处用IK
+			indexWriter = new IndexWriter(directory, config);
+		}
+		return indexWriter;
+	}
+
+	/**
+	 * 释放对indexWriter的使用
+	 */
+	public synchronized void release() {
+		// indexWriter一直保持打开状态,也只存在一个实例,
+		// indexWriter用完后只是释放对其的占用,不会进行close
+		// TODO close方法有时会卡住(多出现在插入记录之后),暂时还未找到原因
+		doneUsing();
+	}
+
+	private boolean using = false;
+
+	private synchronized void startUsing() throws InterruptedException {
+		while (using) {
+			wait();
+		}
+		using = true;
+	}
+
+	private synchronized void doneUsing() {
+		using = false;
+		notifyAll();
+	}
+}

+ 84 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/util/DocumentToObjectUtils.java

@@ -0,0 +1,84 @@
+package com.uas.search.console.b2b.util;
+
+import java.util.HashSet;
+import java.util.Set;
+
+import org.apache.lucene.document.Document;
+
+import com.alibaba.dubbo.common.utils.StringUtils;
+import com.alibaba.fastjson.JSONObject;
+import com.uas.search.console.b2b.model.EnterpriseSimpleInfo;
+import com.uas.search.console.b2b.model.MakeOrderSimpleInfo;
+import com.uas.search.console.b2b.model.ProductSimpleInfo;
+import com.uas.search.console.b2b.model.PurchaseOrderItemSimpleInfo;
+import com.uas.search.console.b2b.model.PurchaseOrderSimpleInfo;
+
+/**
+ * 将Document转换为对象的工具类
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 下午3:31:06
+ */
+public class DocumentToObjectUtils {
+
+	/**
+	 * 将Document转换为PurchaseOrder对象
+	 * 
+	 * @param document
+	 * @return
+	 */
+	public static PurchaseOrderSimpleInfo toPurchaseOrder(Document document) {
+		if (document == null) {
+			return null;
+		}
+		PurchaseOrderSimpleInfo purchaseOrder = new PurchaseOrderSimpleInfo();
+		purchaseOrder.setId(Long.valueOf(document.get(PurchaseOrderSimpleInfo.ID_FIELD)));
+		purchaseOrder.setCode(document.get(PurchaseOrderSimpleInfo.CODE_FIELD));
+		purchaseOrder.setStatus(Short.valueOf(document.get(PurchaseOrderSimpleInfo.STATUS_FIELD)));
+		purchaseOrder.setVend(
+				JSONObject.parseObject(document.get(PurchaseOrderSimpleInfo.VEND_FIELD), EnterpriseSimpleInfo.class));
+		purchaseOrder.setOrderItems(
+				toSet(document.get(PurchaseOrderSimpleInfo.ITEMS_FIELD), PurchaseOrderItemSimpleInfo.class));
+		return purchaseOrder;
+	}
+
+	/**
+	 * 将Document转换为MakeOrder对象
+	 * 
+	 * @param document
+	 * @return
+	 */
+	public static MakeOrderSimpleInfo toMakeOrder(Document document) {
+		if (document == null) {
+			return null;
+		}
+		MakeOrderSimpleInfo makeOrder = new MakeOrderSimpleInfo();
+		makeOrder.setId(Long.valueOf(document.get(MakeOrderSimpleInfo.ID_FIELD)));
+		makeOrder.setCode(document.get(MakeOrderSimpleInfo.CODE_FIELD));
+		makeOrder.setStatus(Short.valueOf(document.get(MakeOrderSimpleInfo.STATUS_FIELD)));
+		makeOrder.setVend(
+				JSONObject.parseObject(document.get(MakeOrderSimpleInfo.VEND_FIELD), EnterpriseSimpleInfo.class));
+		makeOrder.setProduct(
+				JSONObject.parseObject(document.get(MakeOrderSimpleInfo.PRODUCT_FIELD), ProductSimpleInfo.class));
+		return makeOrder;
+	}
+
+	/**
+	 * 将json字符串转为Set<T>对象
+	 * 
+	 * @param <T>
+	 * 
+	 * @param jsonString
+	 *            索引中存储的json数据
+	 * @return Set<T>对象
+	 */
+	public static <T> Set<T> toSet(String jsonString, Class<T> clazz) {
+		if (StringUtils.isEmpty(jsonString)) {
+			return null;
+		}
+		Set<T> set = new HashSet<>();
+		set.addAll(JSONObject.parseArray(jsonString, clazz));
+		return set;
+	}
+
+}

+ 107 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/util/ObjectToDocumentUtils.java

@@ -0,0 +1,107 @@
+package com.uas.search.console.b2b.util;
+
+import org.apache.commons.collections.CollectionUtils;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.document.Field.Store;
+import org.apache.lucene.document.StringField;
+import org.apache.lucene.document.TextField;
+import org.springframework.util.StringUtils;
+
+import com.alibaba.fastjson.JSONObject;
+import com.uas.search.console.b2b.model.MakeOrderSimpleInfo;
+import com.uas.search.console.b2b.model.PurchaseOrderSimpleInfo;
+
+import net.sf.ehcache.search.SearchException;
+
+/**
+ * 将对象转换为Document的工具类
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 上午11:49:52
+ */
+// TODO 只用于测试
+public class ObjectToDocumentUtils {
+
+	/**
+	 * 将对象转为Document
+	 * 
+	 * @param object
+	 *            对象,可为PurchaseOrderSimpleInfo、MakeOrderSimpleInfo.....
+	 * @return 转换的Document
+	 */
+	public static Document toDocument(Object object) {
+		if (object == null) {
+			return null;
+		}
+		if (object instanceof PurchaseOrderSimpleInfo) {
+			return toDocument((PurchaseOrderSimpleInfo) object);
+		} else if (object instanceof MakeOrderSimpleInfo) {
+			return toDocument((MakeOrderSimpleInfo) object);
+		}
+		// TODO 其他表
+		else {
+			throw new SearchException("不支持将以下类型转换为Document:" + object.getClass().getName());
+		}
+	}
+
+	/**
+	 * PurchaseOrderSimpleInfo对象转为Document
+	 * 
+	 * @param purchaseOrder
+	 * @return 转换的Document
+	 */
+	public static Document toDocument(PurchaseOrderSimpleInfo purchaseOrder) {
+		if (purchaseOrder == null || purchaseOrder.getId() == null || StringUtils.isEmpty(purchaseOrder.getCode())
+				|| purchaseOrder.getStatus() == null || purchaseOrder.getVend() == null
+				|| purchaseOrder.getVend().getUu() == null
+				|| StringUtils.isEmpty(purchaseOrder.getVend().getEnName())) {
+			return null;
+		}
+		Document document = new Document();
+		// 不能用LongField,否则后续实时更新索引时,方法updateDocument(new Term("", ""),
+		// doc)无法根据id进行更新
+		document.add(
+				new StringField(PurchaseOrderSimpleInfo.ID_FIELD, String.valueOf(purchaseOrder.getId()), Store.YES));
+		document.add(new TextField(PurchaseOrderSimpleInfo.CODE_FIELD, purchaseOrder.getCode(), Store.YES));
+		document.add(new StringField(PurchaseOrderSimpleInfo.STATUS_FIELD, String.valueOf(purchaseOrder.getStatus()),
+				Store.YES));
+		// vend和orderItems以json的格式存储
+		document.add(new TextField(PurchaseOrderSimpleInfo.VEND_FIELD, JSONObject.toJSONString(purchaseOrder.getVend()),
+				Store.YES));
+		if (!CollectionUtils.isEmpty(purchaseOrder.getOrderItems())) {
+			document.add(new TextField(PurchaseOrderSimpleInfo.ITEMS_FIELD,
+					JSONObject.toJSONString(purchaseOrder.getOrderItems()), Store.YES));
+		}
+		return document;
+	}
+
+	/**
+	 * MakeOrderSimpleInfo对象转为Document
+	 * 
+	 * @param makeOrder
+	 * @return 转换的Document
+	 */
+	public static Document toDocument(MakeOrderSimpleInfo makeOrder) {
+		if (makeOrder == null || makeOrder.getId() == null || StringUtils.isEmpty(makeOrder.getCode())
+				|| makeOrder.getStatus() == null || makeOrder.getVend() == null || makeOrder.getVend().getUu() == null
+				|| StringUtils.isEmpty(makeOrder.getVend().getEnName())) {
+			return null;
+		}
+		Document document = new Document();
+		// 不能用LongField,否则后续实时更新索引时,方法updateDocument(new Term("", ""),
+		// doc)无法根据id进行更新
+		document.add(new StringField(MakeOrderSimpleInfo.ID_FIELD, String.valueOf(makeOrder.getId()), Store.YES));
+		document.add(new TextField(MakeOrderSimpleInfo.CODE_FIELD, makeOrder.getCode(), Store.YES));
+		document.add(
+				new StringField(MakeOrderSimpleInfo.STATUS_FIELD, String.valueOf(makeOrder.getStatus()), Store.YES));
+		// vend和product以json的格式存储
+		document.add(
+				new TextField(MakeOrderSimpleInfo.VEND_FIELD, JSONObject.toJSONString(makeOrder.getVend()), Store.YES));
+		if (makeOrder.getProduct() != null) {
+			document.add(new TextField(MakeOrderSimpleInfo.PRODUCT_FIELD,
+					JSONObject.toJSONString(makeOrder.getProduct()), Store.YES));
+		}
+		return document;
+	}
+
+}

+ 36 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/util/SearchConstants.java

@@ -0,0 +1,36 @@
+package com.uas.search.console.b2b.util;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.wltea.analyzer.lucene.IKAnalyzer;
+
+import com.uas.search.console.b2b.core.util.PathUtils;
+
+/**
+ * 索引相关的常量
+ * 
+ * @author sunyj
+ * @since 2016年11月9日 上午11:23:25
+ */
+public class SearchConstants {
+
+	/**
+	 * IK分词解析器
+	 */
+	public static final Analyzer IK_ANALYZER = new IKAnalyzer();
+
+	/**
+	 * 默认搜索的最大的记录条数
+	 */
+	public static final int TOP_NUM = 1024 * 1024 * 1024;
+
+	/**
+	 * 消息队列名,该队列存放kind、brand、component三个表的改动信息
+	 */
+	public static final String LUCENE_QUEUE_NAME = "lucene_queue_b2b";
+
+	/**
+	 * 索引文件存储路径
+	 */
+	public static final String INDEX_DIR = PathUtils.getFilePath() + "indexes";
+
+}

+ 270 - 0
search-console-b2b/src/main/java/com/uas/search/console/b2b/util/SearchUtils.java

@@ -0,0 +1,270 @@
+package com.uas.search.console.b2b.util;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.lucene.analysis.Analyzer;
+import org.apache.lucene.analysis.TokenStream;
+import org.apache.lucene.analysis.tokenattributes.CharTermAttribute;
+import org.apache.lucene.document.Document;
+import org.apache.lucene.index.Term;
+import org.apache.lucene.search.BooleanClause;
+import org.apache.lucene.search.BooleanQuery;
+import org.apache.lucene.search.IndexSearcher;
+import org.apache.lucene.search.PrefixQuery;
+import org.apache.lucene.search.Query;
+import org.apache.lucene.search.ScoreDoc;
+import org.apache.lucene.search.TermQuery;
+import org.apache.lucene.search.TopDocs;
+import org.springframework.util.CollectionUtils;
+import org.springframework.util.StringUtils;
+import org.wltea.analyzer.lucene.IKAnalyzer;
+
+import com.uas.search.b2b.exception.SearchException;
+import com.uas.search.b2b.service.SearchService.Table_name;
+import com.uas.search.console.b2b.model.MakeOrderSimpleInfo;
+import com.uas.search.console.b2b.model.PurchaseOrderSimpleInfo;
+import com.uas.search.console.b2b.support.IndexSearcherManager;
+
+/**
+ * 搜索相关的工具类
+ * 
+ * @author sunyj
+ * @since 2016年10月17日 下午1:56:16
+ */
+public class SearchUtils {
+
+	/**
+	 * IndexSearcher的管理器
+	 */
+	private static IndexSearcherManager searcherManager = new IndexSearcherManager();
+
+	/**
+	 * 判断搜索词是否为无效的(比如只包含特殊字符,不含有任何字母、数字、汉字等有意义的字符)
+	 * 
+	 * @param keyword
+	 * @return 搜索词是否无效
+	 */
+	public static boolean isKeywordInvalid(String keyword) {
+		if (!StringUtils.isEmpty(keyword)) {
+			// 将特殊字符剔除
+			keyword = keyword.replaceAll("[`~!@#$^&*()=|{}':;',\\[\\].<>/?~!@#¥……&*()——|{}【】‘;:”“'。,、? ]+", "");
+			return StringUtils.isEmpty(keyword);
+		}
+		return true;
+	}
+
+	/**
+	 * 获取每个单据可以搜索的字段列名(field名)
+	 * 
+	 * @param tableName
+	 *            单据类型
+	 * @return 可以搜索的字段列名(field名)
+	 */
+	public static List<String> getKeywordFields(Table_name tableName) {
+		if (tableName == Table_name.PURC$ORDERS) {
+			return PurchaseOrderSimpleInfo.getAllExceptStatusFields();
+		} else if (tableName == Table_name.MAKE$ORDERS) {
+			return MakeOrderSimpleInfo.getAllExceptStatusFields();
+		}
+
+		// TODO 其他表
+
+		else {
+			throw new SearchException("表不存在:" + tableName.value());
+		}
+	}
+
+	/**
+	 * 获取每个单据的id字段的列名(field名)
+	 * 
+	 * @param tableName
+	 *            单据类型
+	 * @return id字段的列名(field名)
+	 */
+	public static String getIdField(Table_name tableName) {
+		if (tableName == Table_name.PURC$ORDERS) {
+			return PurchaseOrderSimpleInfo.ID_FIELD;
+		} else if (tableName == Table_name.MAKE$ORDERS) {
+			return MakeOrderSimpleInfo.ID_FIELD;
+		}
+
+		// TODO 其他表
+
+		else {
+			throw new SearchException("表不存在:" + tableName.value());
+		}
+	}
+
+	/**
+	 * 获取每个单据的status状态(只进行过滤,不进行搜索)字段的列名(field名)
+	 * 
+	 * @param tableName
+	 *            单据类型
+	 * @return status状态字段的列名(field名)
+	 */
+	public static String getStatusField(Table_name tableName) {
+		if (tableName == Table_name.PURC$ORDERS) {
+			return PurchaseOrderSimpleInfo.STATUS_FIELD;
+		} else if (tableName == Table_name.MAKE$ORDERS) {
+			return MakeOrderSimpleInfo.STATUS_FIELD;
+		}
+
+		// TODO 其他表
+
+		else {
+			throw new SearchException("表不存在:" + tableName.value());
+		}
+	}
+
+	/**
+	 * 对搜索词进行分词后组合得到BooleanQuery
+	 * 
+	 * @param field
+	 *            搜索的域名
+	 * @param keyword
+	 *            搜索关键词
+	 * @return
+	 */
+	public static BooleanQuery getBooleanQuery(String field, String keyword) {
+		if (StringUtils.isEmpty(field) || StringUtils.isEmpty(keyword)) {
+			return null;
+		}
+		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 PrefixQuery(new Term(field, cta.toString())), BooleanClause.Occur.MUST);
+			}
+			tokenStream.close();
+			analyzer.close();
+		} catch (IOException e) {
+			e.printStackTrace();
+		}
+		return booleanQuery;
+	}
+
+	/**
+	 * 获取IndexSearcher对象,若为空,抛出异常
+	 * 
+	 * @return IndexSearcher对象
+	 */
+	public static IndexSearcher getIndexSearcher() {
+		searcherManager.maybeReopen();
+		IndexSearcher indexSearcher = searcherManager.get();
+		if (indexSearcher == null) {
+			throw new SearchException("获取索引文件失败");
+		}
+		return indexSearcher;
+	}
+
+	/**
+	 * 释放indexSearcher
+	 * 
+	 * @param indexSearcher
+	 */
+	public static void releaseIndexSearcher(IndexSearcher indexSearcher) {
+		searcherManager.release(indexSearcher);
+	}
+
+	/**
+	 * 根据域和搜索词获取Document(每个id最多只能对应一条数据)
+	 * 
+	 * @param field
+	 *            搜索域
+	 * @param keyword
+	 *            搜索词
+	 * @return Document
+	 */
+	public static Document getDocumentById(String field, Long id) {
+		if (id == null) {
+			throw new SearchException("输入无效:" + id);
+		}
+		List<Document> documents = getDocuments(field, Long.toString(id));
+		if (CollectionUtils.isEmpty(documents)) {
+			return null;
+		} else if (documents.size() > 1) {
+			throw new SearchException("索引中存在不止一个对象:" + field + "=" + id);
+		}
+		return documents.get(0);
+	}
+
+	/**
+	 * 根据域和搜索词获取Document列表
+	 * 
+	 * @param field
+	 *            搜索域
+	 * @param keyword
+	 *            搜索词
+	 * @return Document列表
+	 */
+	public static List<Document> getDocuments(String field, String keyword) {
+		return getDocuments(field, keyword, SearchConstants.TOP_NUM);
+	}
+
+	/**
+	 * 根据域和搜索词获取指定数目的Document列表
+	 * 
+	 * @param field
+	 *            搜索域
+	 * @param keyword
+	 *            搜索词
+	 * @param topNum
+	 *            指定的数目(数目最多为该数目)
+	 * @return
+	 */
+	public static List<Document> getDocuments(String field, String keyword, int topNum) {
+		if (StringUtils.isEmpty(field) || StringUtils.isEmpty(keyword)) {
+			throw new SearchException("搜索的域和搜索词不能为空");
+		}
+		TermQuery termQuery = new TermQuery(new Term(field, keyword));
+		return getDocuments(termQuery, topNum);
+	}
+
+	/**
+	 * 根据查询条件获取Document列表
+	 * 
+	 * @param query
+	 *            查询条件
+	 * @return
+	 */
+	public static List<Document> getDocuments(Query query) {
+		return getDocuments(query, SearchConstants.TOP_NUM);
+	}
+
+	/**
+	 * 根据查询条件获取指定数目的Document列表
+	 * 
+	 * @param query
+	 *            查询条件
+	 * @param topNum
+	 *            指定的数目(数目最多为该数目)
+	 * @return
+	 */
+	public static List<Document> getDocuments(Query query, int topNum) {
+		if (query == null) {
+			throw new SearchException("query不能为null");
+		}
+		if (topNum < 1) {
+			throw new SearchException("查询的数目topNum不能小于1");
+		}
+		IndexSearcher indexSearcher = getIndexSearcher();
+		List<Document> documents = new ArrayList<>();
+		try {
+			TopDocs topDocs = indexSearcher.search(query, topNum);
+			ScoreDoc[] scoreDocs = topDocs.scoreDocs;
+			for (ScoreDoc scoreDoc : scoreDocs) {
+				documents.add(indexSearcher.doc(scoreDoc.doc));
+			}
+		} catch (IOException e) {
+			throw new SearchException(e).setDetailedMessage(e);
+		} finally {
+			releaseIndexSearcher(indexSearcher);
+		}
+		return documents;
+	}
+}

+ 2 - 0
search-console-b2b/src/main/resources/dev/dubbo.properties

@@ -0,0 +1,2 @@
+dubbo.owner=test
+dubbo.group=test

+ 12 - 0
search-console-b2b/src/main/resources/dev/jdbc.properties

@@ -0,0 +1,12 @@
+#test model
+jdbc.driverClassName=oracle.jdbc.driver.OracleDriver
+jdbc.url=jdbc:oracle:thin:@192.168.253.6:1521:orcl
+jdbc.username=uuplatformdemo
+jdbc.password=selectuuplatform
+jdbc.initialSize=10
+jdbc.maxActive=100
+jdbc.maxIdle=50
+jdbc.minIdle=50
+jdbc.suspectTimeout=60
+jdbc.timeBetweenEvictionRunsMillis=30000
+jdbc.minEvictableIdleTimeMillis=60000

+ 15 - 0
search-console-b2b/src/main/resources/dev/logging.properties

@@ -0,0 +1,15 @@
+#Tomcat Logger
+
+handlers = org.apache.juli.FileHandler, java.util.logging.ConsoleHandler   
+  
+############################################################   
+# Handler specific properties.   
+# Describes specific configuration info for Handlers.   
+############################################################   
+  
+org.apache.juli.FileHandler.level = FINE   
+org.apache.juli.FileHandler.directory = ${catalina.base}/logs   
+org.apache.juli.FileHandler.prefix = error-debug.   
+  
+java.util.logging.ConsoleHandler.level = FINE   
+java.util.logging.ConsoleHandler.formatter = java.util.logging.SimpleFormatter

+ 127 - 0
search-console-b2b/src/main/resources/spring/applicationContext.xml

@@ -0,0 +1,127 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:context="http://www.springframework.org/schema/context"
+	xmlns:aop="http://www.springframework.org/schema/aop" xmlns:task="http://www.springframework.org/schema/task"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
+	xmlns:cache="http://www.springframework.org/schema/cache" xmlns:util="http://www.springframework.org/schema/util"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-4.1.xsd   
+	http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-4.1.xsd
+	http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-4.1.xsd
+	http://www.springframework.org/schema/task http://www.springframework.org/schema/task/spring-task-4.1.xsd
+	http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-4.1.xsd
+	http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd
+	http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache-4.1.xsd">
+
+	<context:property-placeholder location="classpath*:${profile}/*.properties" />
+	<!-- 注册spring上下文对象 -->
+	<bean class="com.uas.search.console.b2b.support.ApplicationContextRegister" />
+
+	<!-- 扫描注解 -->
+	<context:annotation-config />
+	<!-- 扫描的包 -->
+	<context:component-scan base-package="com.uas.search.console.b2b" />
+
+	<!-- dataSource -->
+	<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource"
+		destroy-method="close">
+		<property name="driverClassName" value="${jdbc.driverClassName}" />
+		<property name="url" value="${jdbc.url}" />
+		<property name="username" value="${jdbc.username}" />
+		<property name="password" value="${jdbc.password}" />
+		<!-- 连接初始值,连接池启动时创建的连接数量的初始值 -->
+		<property name="initialSize" value="${jdbc.initialSize}" />
+		<!-- 连接池的最大值,同一时间可以从池分配的最多连接数量,0时无限制 -->
+		<property name="maxActive" value="${jdbc.maxActive}" />
+		<!-- 最大空闲值.当经过一个高峰时间后,连接池可以慢慢将已经用不到的连接慢慢释放一部分,一直减少到maxIdle为止 ,0时无限制 -->
+		<property name="maxIdle" value="${jdbc.maxIdle}" />
+		<!-- 最小空闲值.当空闲的连接数少于阀值时,连接池就会预申请去一些连接,以免洪峰来时来不及申请 -->
+		<property name="minIdle" value="${jdbc.minIdle}" />
+		<!-- 是否对已备语句进行池管理(布尔值),是否对PreparedStatement进行缓存 -->
+		<property name="poolPreparedStatements" value="true" />
+		<!-- 是否对sql进行自动提交 -->
+		<property name="defaultAutoCommit" value="true" />
+		<!-- 回收超时连接 -->
+		<property name="removeAbandoned" value="true" />
+		<!-- 连接空闲时校验连接有效性 -->
+		<property name="testWhileIdle" value="true" />
+		<!-- 校验连接有效性的sql -->
+		<property name="validationQuery" value="select 1 from dual" />
+		<!-- 每过timeBetweenEvictionRunsMillis 时间,就会启动一个线程,校验连接池中闲置时间超过minEvictableIdleTimeMillis的连接对象 -->
+		<property name="timeBetweenEvictionRunsMillis" value="${jdbc.timeBetweenEvictionRunsMillis}" />
+		<property name="minEvictableIdleTimeMillis" value="${jdbc.minEvictableIdleTimeMillis}" />
+	</bean>
+	<!-- hibernate jpa -->
+	<bean class="org.springframework.orm.jpa.JpaTransactionManager"
+		id="transactionManager">
+		<property name="entityManagerFactory" ref="entityManagerFactory" />
+	</bean>
+	<tx:annotation-driven transaction-manager="transactionManager" />
+
+	<bean id="transactionInterceptor"
+		class="org.springframework.transaction.interceptor.TransactionInterceptor">
+		<property name="transactionManager" ref="transactionManager" />
+		<property name="transactionAttributeSource">
+			<bean
+				class="org.springframework.transaction.annotation.AnnotationTransactionAttributeSource"></bean>
+		</property>
+	</bean>
+
+	<bean id="transactionAttributeSourceAdvisor"
+		class="org.springframework.transaction.interceptor.TransactionAttributeSourceAdvisor">
+		<property name="transactionInterceptor" ref="transactionInterceptor" />
+	</bean>
+
+	<bean id="entityManagerFactory"
+		class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
+		<property name="persistenceUnitName" value="persistenceUnit" />
+		<!-- <property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.xml" 
+			/> -->
+		<property name="packagesToScan" value="com.uas.search.console" />
+		<property name="dataSource" ref="dataSource" />
+		<property name="jpaVendorAdapter">
+			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter">
+				<property name="generateDdl" value="false" />
+				<property name="databasePlatform" value="org.hibernate.dialect.Oracle10gDialect" />
+				<property name="showSql" value="false" />
+			</bean>
+		</property>
+		<property name="jpaDialect">
+			<bean class="org.springframework.orm.jpa.vendor.HibernateJpaDialect" />
+		</property>
+		<property name="jpaProperties">
+			<props>
+				<prop key="hibernate.cache.provider_configuration_file_resource_path">classpath:spring/ehcache.xml</prop>
+				<prop key="hibernate.cache.region.factory_class">org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory
+				</prop>
+				<prop key="hibernate.cache.use_query_cache">true</prop>
+				<prop key="hibernate.cache.use_second_level_cache">true</prop>
+				<prop key="hibernate.generate_statistics">true</prop>
+				<prop key="hibernate.use_sql_comments">true</prop>
+				<prop key="hibernate.format_sql">true</prop>
+				<prop key="hibernate.generate_statistics">true</prop>
+				<prop key="hibernate.enable_lazy_load_no_trans">true</prop>
+			</props>
+		</property>
+	</bean>
+	<!-- jdbctemplate -->
+	<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
+		<property name="dataSource" ref="dataSource" />
+	</bean>
+	<!-- 使用ehcache对象缓存 -->
+	<cache:annotation-driven />
+	<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheCacheManager">
+		<property name="cacheManager" ref="ehcache"></property>
+	</bean>
+	<bean id="ehcache"
+		class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
+		<property name="configLocation" value="classpath:spring/ehcache.xml"></property>
+		<property name="shared" value="true"></property>
+	</bean>
+
+	<!-- jpa -->
+	<import resource="jpa.xml" />
+
+	<!-- dubbo 服务 -->
+	<import resource="provider.xml" />
+
+</beans>

+ 8 - 0
search-console-b2b/src/main/resources/spring/ehcache.xml

@@ -0,0 +1,8 @@
+<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+	xsi:noNamespaceSchemaLocation="classpath:ehcache.xsd">
+	<diskStore path="java.io.tmpdir" />
+	<defaultCache maxElementsInMemory="10000" eternal="false"
+		timeToIdleSeconds="120" overflowToDisk="true" maxElementsOnDisk="10000000"
+		diskPersistent="false" diskExpiryThreadIntervalSeconds="120"
+		timeToLiveSeconds="8" memoryStoreEvictionPolicy="LRU" />
+</ehcache>

+ 270 - 0
search-console-b2b/src/main/resources/spring/ehcache.xsd

@@ -0,0 +1,270 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema" elementFormDefault="qualified" version="1.7">
+
+    <xs:element name="ehcache">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element maxOccurs="1" minOccurs="0" ref="diskStore"/>
+                <xs:element maxOccurs="1" minOccurs="0" ref="transactionManagerLookup"/>
+                <xs:element maxOccurs="1" minOccurs="0" ref="cacheManagerEventListenerFactory"/>
+                <xs:element maxOccurs="unbounded" minOccurs="0" ref="cacheManagerPeerProviderFactory"/>
+                <xs:element maxOccurs="unbounded" minOccurs="0" ref="cacheManagerPeerListenerFactory"/>
+                <xs:element maxOccurs="1" minOccurs="0" ref="terracottaConfig"/>
+                <xs:element ref="defaultCache"/>
+                <xs:element maxOccurs="unbounded" minOccurs="0" ref="cache"/>
+            </xs:sequence>
+            <xs:attribute name="name" use="optional"/>
+            <xs:attribute default="true" name="updateCheck" type="xs:boolean" use="optional"/>
+            <xs:attribute default="autodetect" name="monitoring" type="monitoringType" use="optional"/>
+            <xs:attribute default="true" name="dynamicConfig" type="xs:boolean" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="diskStore">
+        <xs:complexType>
+            <xs:attribute name="path" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+     <xs:element name="transactionManagerLookup">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="cacheManagerEventListenerFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="cacheManagerPeerProviderFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="cacheManagerPeerListenerFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="terracottaConfig">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element maxOccurs="1" minOccurs="0" name="tc-config">
+                    <xs:complexType>
+                        <xs:sequence>
+                            <xs:any maxOccurs="unbounded" minOccurs="0" processContents="skip"/>
+                        </xs:sequence>
+                    </xs:complexType>
+                </xs:element>
+            </xs:sequence>
+            <xs:attribute default="localhost:9510" name="url" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <!-- add clone support for addition of cacheExceptionHandler. Important! -->
+    <xs:element name="defaultCache">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheDecoratorFactory"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="terracotta"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="cacheWriter"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="copyStrategy"/>
+            </xs:sequence>
+            <xs:attribute name="diskExpiryThreadIntervalSeconds" type="xs:integer" use="optional"/>
+            <xs:attribute name="diskSpoolBufferSizeMB" type="xs:integer" use="optional"/>
+            <xs:attribute name="diskPersistent" type="xs:boolean" use="optional"/>
+            <xs:attribute name="diskAccessStripes" type="xs:integer" use="optional" default="1"/>
+            <xs:attribute name="eternal" type="xs:boolean" use="required"/>
+            <xs:attribute name="maxElementsInMemory" type="xs:integer" use="required"/>
+            <xs:attribute name="clearOnFlush" type="xs:boolean" use="optional"/>
+            <xs:attribute name="memoryStoreEvictionPolicy" type="xs:string" use="optional"/>
+            <xs:attribute name="overflowToDisk" type="xs:boolean" use="required"/>
+            <xs:attribute name="timeToIdleSeconds" type="xs:integer" use="optional"/>
+            <xs:attribute name="timeToLiveSeconds" type="xs:integer" use="optional"/>
+            <xs:attribute name="maxElementsOnDisk" type="xs:integer" use="optional"/>
+            <xs:attribute name="transactionalMode" type="transactionalMode" use="optional" default="off"/>
+            <xs:attribute name="statistics" type="xs:boolean" use="optional" default="false"/>
+            <xs:attribute name="copyOnRead" type="xs:boolean" use="optional" default="false"/>
+            <xs:attribute name="copyOnWrite" type="xs:boolean" use="optional" default="false"/>
+            <xs:attribute name="cacheLoaderTimeoutMillis" type="xs:integer" use="optional" default="0"/>
+            <xs:attribute name="overflowToOffHeap" type="xs:boolean" use="optional" default="false"/>
+            <xs:attribute name="maxMemoryOffHeap" type="xs:string" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="cache">
+        <xs:complexType>
+            <xs:sequence>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheEventListenerFactory"/>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheExtensionFactory"/>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheLoaderFactory"/>
+                <xs:element minOccurs="0" maxOccurs="unbounded" ref="cacheDecoratorFactory"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="bootstrapCacheLoaderFactory"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="cacheExceptionHandlerFactory"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="terracotta"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="cacheWriter"/>
+                <xs:element minOccurs="0" maxOccurs="1" ref="copyStrategy"/>
+            </xs:sequence>
+            <xs:attribute name="diskExpiryThreadIntervalSeconds" type="xs:integer" use="optional"/>
+            <xs:attribute name="diskSpoolBufferSizeMB" type="xs:integer" use="optional"/>
+            <xs:attribute name="diskPersistent" type="xs:boolean" use="optional"/>
+            <xs:attribute name="diskAccessStripes" type="xs:integer" use="optional" default="1"/>
+            <xs:attribute name="eternal" type="xs:boolean" use="required"/>
+            <xs:attribute name="maxElementsInMemory" type="xs:integer" use="required"/>
+            <xs:attribute name="memoryStoreEvictionPolicy" type="xs:string" use="optional"/>
+            <xs:attribute name="clearOnFlush" type="xs:boolean" use="optional"/>
+            <xs:attribute name="name" type="xs:string" use="required"/>
+            <xs:attribute name="overflowToDisk" type="xs:boolean" use="required"/>
+            <xs:attribute name="timeToIdleSeconds" type="xs:integer" use="optional"/>
+            <xs:attribute name="timeToLiveSeconds" type="xs:integer" use="optional"/>
+            <xs:attribute name="maxElementsOnDisk" type="xs:integer" use="optional"/>
+            <xs:attribute name="transactionalMode" type="transactionalMode" use="optional" default="off" />
+            <xs:attribute name="statistics" type="xs:boolean" use="optional" default="false"/>
+            <xs:attribute name="copyOnRead" type="xs:boolean" use="optional" default="false"/>
+            <xs:attribute name="copyOnWrite" type="xs:boolean" use="optional" default="false"/>
+            <xs:attribute name="logging" type="xs:boolean" use="optional" default="false"/>
+            <xs:attribute name="cacheLoaderTimeoutMillis" type="xs:integer" use="optional" default="0"/>
+            <xs:attribute name="overflowToOffHeap" type="xs:boolean" use="optional" default="false"/>
+            <xs:attribute name="maxMemoryOffHeap" type="xs:string" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="cacheEventListenerFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+            <xs:attribute name="listenFor" use="optional" type="notificationScope" default="all"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="bootstrapCacheLoaderFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="cacheExtensionFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="cacheExceptionHandlerFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="cacheLoaderFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="cacheDecoratorFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:element name="terracotta">
+        <xs:complexType>
+            <xs:attribute name="clustered" use="optional" type="xs:boolean" default="true"/>
+            <xs:attribute name="valueMode" use="optional" type="terracottaCacheValueType" default="serialization"/>
+            <xs:attribute name="coherentReads" use="optional" type="xs:boolean" default="true"/>
+            <xs:attribute name="localKeyCache" use="optional" type="xs:boolean" default="false"/>
+            <xs:attribute name="localKeyCacheSize" use="optional" type="xs:positiveInteger" default="300000"/>
+            <xs:attribute name="orphanEviction" use="optional" type="xs:boolean" default="true"/>
+            <xs:attribute name="orphanEvictionPeriod" use="optional" type="xs:positiveInteger" default="4"/>
+            <xs:attribute name="copyOnRead" use="optional" type="xs:boolean" default="false"/>
+            <xs:attribute name="coherent" use="optional" type="xs:boolean" default="true"/>
+            <xs:attribute name="synchronousWrites" use="optional" type="xs:boolean" default="false"/>
+            <xs:attribute name="storageStrategy" use="optional" type="storageStrategyType" default="classic"/>
+            <xs:attribute name="concurrency" use="optional" type="xs:nonNegativeInteger" default="0"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:simpleType name="monitoringType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="autodetect"/>
+            <xs:enumeration value="on"/>
+            <xs:enumeration value="off"/>
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="terracottaCacheValueType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="serialization" />
+            <xs:enumeration value="identity" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:simpleType name="storageStrategyType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="classic" />
+            <xs:enumeration value="DCV2" />
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:simpleType name="transactionalMode">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="off"/>
+            <xs:enumeration value="xa"/>
+        </xs:restriction>
+    </xs:simpleType>
+
+    <xs:element name="cacheWriter">
+        <xs:complexType>
+            <xs:sequence >
+                <xs:element minOccurs="0" maxOccurs="1" ref="cacheWriterFactory"/>
+            </xs:sequence>
+            <xs:attribute name="writeMode" use="optional" type="writeModeType" default="write-through"/>
+            <xs:attribute name="notifyListenersOnException" use="optional" type="xs:boolean" default="false"/>
+            <xs:attribute name="minWriteDelay" use="optional" type="xs:nonNegativeInteger" default="1"/>
+            <xs:attribute name="maxWriteDelay" use="optional" type="xs:nonNegativeInteger" default="1"/>
+            <xs:attribute name="rateLimitPerSecond" use="optional" type="xs:nonNegativeInteger" default="0"/>
+            <xs:attribute name="writeCoalescing" use="optional" type="xs:boolean" default="false"/>
+            <xs:attribute name="writeBatching" use="optional" type="xs:boolean" default="false"/>
+            <xs:attribute name="writeBatchSize" use="optional" type="xs:positiveInteger" default="1"/>
+            <xs:attribute name="retryAttempts" use="optional" type="xs:nonNegativeInteger" default="0"/>
+            <xs:attribute name="retryAttemptDelaySeconds" use="optional" type="xs:nonNegativeInteger" default="1"/>
+        </xs:complexType>
+    </xs:element>
+    <xs:simpleType name="writeModeType">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="write-through" />
+            <xs:enumeration value="write-behind" />
+        </xs:restriction>
+    </xs:simpleType>
+    <xs:element name="cacheWriterFactory">
+        <xs:complexType>
+            <xs:attribute name="class" use="required"/>
+            <xs:attribute name="properties" use="optional"/>
+            <xs:attribute name="propertySeparator" use="optional"/>
+        </xs:complexType>
+    </xs:element>
+
+    <xs:element name="copyStrategy">
+        <xs:complexType>
+            <xs:attribute name="class" use="required" type="xs:string" />
+        </xs:complexType>
+    </xs:element>
+
+    <xs:simpleType name="notificationScope">
+        <xs:restriction base="xs:string">
+            <xs:enumeration value="local"/>
+            <xs:enumeration value="remote"/>
+            <xs:enumeration value="all"/>
+        </xs:restriction>
+    </xs:simpleType>
+</xs:schema>

+ 11 - 0
search-console-b2b/src/main/resources/spring/jpa.xml

@@ -0,0 +1,11 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans:beans xmlns:beans="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://www.springframework.org/schema/data/jpa"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans 
+	http://www.springframework.org/schema/beans/spring-beans.xsd 
+	http://www.springframework.org/schema/data/jpa 
+	http://www.springframework.org/schema/data/jpa/spring-jpa.xsd">
+
+	<repositories base-package="com.uas.search.console.b2b" />
+
+</beans:beans>

+ 18 - 0
search-console-b2b/src/main/resources/spring/provider.xml

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+	xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:dubbo="http://code.alibabatech.com/schema/dubbo"
+	xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
+	http://code.alibabatech.com/schema/dubbo http://code.alibabatech.com/schema/dubbo/dubbo.xsd">
+
+	<dubbo:application name="search_b2b_provider" owner="${dubbo.owner}" />
+
+	<dubbo:registry address="zookeeper://10.10.100.11:2181" />
+
+	<dubbo:protocol name="dubbo" port="20880" />
+
+	<bean id="searchService"
+		class="com.uas.search.console.b2b.service.impl.SearchServiceImpl" />
+
+	<dubbo:service interface="com.uas.search.b2b.service.SearchService"
+		ref="searchService" group="${dubbo.group}" />
+</beans>

+ 39 - 0
search-console-b2b/src/main/webapp/WEB-INF/views/console.html

@@ -0,0 +1,39 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>Search</title>
+<link rel="stylesheet" href="static/css/console.css">
+</head>
+<body>
+	<div id="mainContainer">
+		<ol>
+			<strong><li class="title">搜索</li></strong>
+			<ol>
+				<li><a target="_blank">search?keyword=xxx&tableName=xxx&page=1&size=8&status=xxx</a></li>
+			</ol>
+
+			<strong><li class="title">获取索引中的数据</li></strong>
+			<ol>
+				<li><a target="_blank">search/purchaseOrder/12</a></li>
+			</ol>
+
+			<strong><li class="title">索引修改</li></strong>
+			<ol>
+				<li><a target="_blank">index/create</a></li>
+				<li><a target="_blank">index/listen/start</a></li>
+				<li><a target="_blank">index/listen/stop</a></li>
+				<li><a target="_blank">index/maintain?tableName=PURC$ORDERS&ids=12,328,343</a></li>
+			</ol>
+
+			<strong><li class="title">文件上传</li></strong>
+			<ol>
+				<li><a target="_blank">fileUpload</a></li>
+			</ol>
+		</ol>
+	</div>
+</body>
+
+<script src="static/lib/jquery/jquery.min.js"></script>
+<script src="static/js/console/app.js"></script>
+</html>

+ 27 - 0
search-console-b2b/src/main/webapp/WEB-INF/views/fileUpload.html

@@ -0,0 +1,27 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>文件上传</title>
+<link rel="stylesheet" href="static/css/fileUpload.css">
+<link rel="stylesheet"
+	href="static/lib/fontawesome/css/font-awesome.min.css">
+</head>
+<body>
+	<form method="post" enctype="multipart/form-data">
+		选择要上传的文件<br /> <br /> <input id="file" type="file" name="file" /><br />
+		<br />
+	</form>
+	<button id="upload">上传</button>
+	<br />
+	<div id="progressDiv" hidden="true">
+		<br /> 上传进度:
+		<progress></progress>
+		<br />
+		<p id="progress">0 bytes</p>
+	</div>
+	<p id="result"></p>
+</body>
+<script src="static/lib/jquery/jquery.min.js"></script>
+<script src="static/js/upload/app.js"></script>
+</html>

+ 11 - 0
search-console-b2b/src/main/webapp/WEB-INF/views/index.html

@@ -0,0 +1,11 @@
+<!DOCTYPE html>
+<html>
+<head>
+<meta charset="UTF-8">
+<title>B2B Search</title>
+<link rel="stylesheet" href="static/css/index.css">
+</head>
+<body>
+	<p>B2B Search Home</p>
+</body>
+</html>

+ 17 - 0
search-console-b2b/src/main/webapp/resources/css/console.css

@@ -0,0 +1,17 @@
+body {
+	font-family: courier;
+}
+
+#mainContainer {
+	margin: 20px;
+}
+
+li.title {
+	margin-top: 20px;
+}
+
+li {
+	margin-top: 10px;
+	margin-bottom: 10px;
+	color: #404040;
+}

+ 15 - 0
search-console-b2b/src/main/webapp/resources/css/fileUpload.css

@@ -0,0 +1,15 @@
+* {
+	font-family: microsoft yahei ui;
+}
+
+body {
+	margin: 20px;
+}
+
+#upload {
+	padding: 1px 15px 1px 15px;
+	cursor: pointer;
+	outline: none;
+	border-radius: 10%;
+	background-color: #eee;
+}

File diff suppressed because it is too large
+ 1 - 0
search-console-b2b/src/main/webapp/resources/lib/jquery/jquery.min.js


Some files were not shown because too many files changed in this diff