Ver código fonte

[mall-search] search PCB

sunyj 8 anos atrás
pai
commit
f5efa2c029

+ 90 - 0
mall-search/init.sql

@@ -272,6 +272,60 @@ end
 ;;
 delimiter ;
 
+-- ----------------------------
+-- Triggers structure for table product$pcb
+-- ----------------------------
+DROP TRIGGER IF EXISTS `lucene_pcb_i`;
+delimiter ;;
+CREATE DEFINER = `root`@`%` TRIGGER `lucene_pcb_i` AFTER INSERT ON `product$pcb` FOR EACH ROW begin
+	declare v_table_name varchar(64) default 'pcb_goods';
+    declare v_method_type varchar(6) default 'insert';
+    declare v_data_id int;
+    declare v_data text default 'pcbId';
+    declare v_priority int default 0;
+
+    set v_data_id=new.pcb_id;
+    call enqueue_lucene_message(v_table_name, v_method_type, v_data_id, v_data, v_priority);
+end
+;;
+delimiter ;
+
+-- ----------------------------
+-- Triggers structure for table product$pcb
+-- ----------------------------
+DROP TRIGGER IF EXISTS `lucene_pcb_u`;
+delimiter ;;
+CREATE DEFINER = `root`@`%` TRIGGER `lucene_pcb_u` AFTER UPDATE ON `product$pcb` FOR EACH ROW begin
+	declare v_table_name varchar(64) default 'pcb_goods';
+    declare v_method_type varchar(6) default 'update';
+    declare v_data_id int;
+    declare v_data text default 'pcbId';
+    declare v_priority int default 0;
+
+    set v_data_id=old.pcb_id;
+    call enqueue_lucene_message(v_table_name, v_method_type, v_data_id, v_data, v_priority);
+end
+;;
+delimiter ;
+
+-- ----------------------------
+-- Triggers structure for table product$pcb
+-- ----------------------------
+DROP TRIGGER IF EXISTS `lucene_pcb_d`;
+delimiter ;;
+CREATE DEFINER = `root`@`%` TRIGGER `lucene_pcb_d` AFTER DELETE ON `product$pcb` FOR EACH ROW begin
+	declare v_table_name varchar(64) default 'pcb_goods';
+    declare v_method_type varchar(6) default 'delete';
+    declare v_data_id int;
+    declare v_data text default 'pcbId';
+    declare v_priority int default 0;
+
+    set v_data_id=old.pcb_id;
+    call enqueue_lucene_message(v_table_name, v_method_type, v_data_id, v_data, v_priority);
+end
+;;
+delimiter ;
+
 -- ----------------------------
 -- Triggers structure for table trade$goods
 -- ----------------------------
@@ -295,6 +349,18 @@ CREATE DEFINER = `root`@`%` TRIGGER `lucene_trade_goods_i` AFTER INSERT ON `trad
       set v_data_id=new.go_id;
     end if;
     call enqueue_lucene_message(v_table_name, v_method_type, v_data_id, v_data, v_priority);
+
+    if new.go_productid is not null then
+      set v_data='pcbId';
+      select pcb_id into v_data_id from product$pcb where pcb_productid=new.go_productid;
+      if v_data_id is null then
+        set v_data=concat('go_productid 没有关联的 PCB:', new.go_productid);
+        set v_data_id=new.go_id;
+      end if;
+    else
+      set v_data_id=new.go_id;
+    end if;
+    call enqueue_lucene_message(v_table_name, v_method_type, v_data_id, v_data, v_priority);
 end
 ;;
 delimiter ;
@@ -322,6 +388,18 @@ CREATE DEFINER = `root`@`%` TRIGGER `lucene_trade_goods_u` AFTER UPDATE ON `trad
       set v_data_id=old.go_id;
     end if;
     call enqueue_lucene_message(v_table_name, v_method_type, v_data_id, v_data, v_priority);
+
+    if old.go_productid is not null then
+      set v_data='pcbId';
+      select pcb_id into v_data_id from product$pcb where pcb_productid=old.go_productid;
+      if v_data_id is null then
+        set v_data=concat('go_productid 没有关联的 PCB:', old.go_productid);
+        set v_data_id=old.go_id;
+      end if;
+    else
+      set v_data_id=old.go_id;
+    end if;
+    call enqueue_lucene_message(v_table_name, v_method_type, v_data_id, v_data, v_priority);
 end
 ;;
 delimiter ;
@@ -349,6 +427,18 @@ CREATE DEFINER = `root`@`%` TRIGGER `lucene_trade_goods_d` AFTER DELETE ON `trad
       set v_data_id=old.go_id;
     end if;
     call enqueue_lucene_message(v_table_name, v_method_type, v_data_id, v_data, v_priority);
+
+    if old.go_productid is not null then
+      set v_data='pcbId';
+      select pcb_id into v_data_id from product$pcb where pcb_productid=old.go_productid;
+      if v_data_id is null then
+        set v_data=concat('go_productid 没有关联的 PCB:', old.go_productid);
+        set v_data_id=old.go_id;
+      end if;
+    else
+      set v_data_id=old.go_id;
+    end if;
+    call enqueue_lucene_message(v_table_name, v_method_type, v_data_id, v_data, v_priority);
 end
 ;;
 delimiter ;

+ 38 - 1
mall-search/src/main/java/com/uas/search/constant/SearchConstants.java

@@ -29,10 +29,20 @@ public class SearchConstants {
 	public static final String COMPONENT_TABLE_NAME = "product$component";
 
 	/**
-	 * 器件表名
+	 * 批次表名(不必真实存在)
 	 */
 	public static final String GOODS_TABLE_NAME = "v$product$cmpgoods";
 
+    /**
+     * PCB 表名
+     */
+    public static final String PCB_TABLE_NAME = "product$pcb";
+
+    /**
+     * PCB 批次表名(不必真实存在)
+     */
+    public static final String PCB_GOODS_TABLE_NAME = "pcb_goods";
+
 	/**
 	 * 商城销售订单表名
 	 */
@@ -147,6 +157,33 @@ public class SearchConstants {
 	// 自动生成的字段,用于唯一区分批次索引
     public static final String GOODS_PRIMARY_KEY_FIELD = "primary_key";
 
+    // PCB 批次索引字段的key
+    public static final String PCB_GOODS_GO_ID_FIELD = "go_id";
+    public static final String PCB_GOODS_GO_RESERVE_FIELD = "go_reserve";
+    public static final String PCB_GOODS_GO_STATUS_FIELD = "go_status";
+    public static final String PCB_GOODS_GO_MINPRICERMB_FIELD = "go_minpricermb";
+    public static final String PCB_GOODS_GO_MINPRICEUSD_FIELD = "go_minpriceusd";
+    public static final String PCB_GOODS_GO_VISIT_COUNT_FIELD = "go_visit_count";
+    public static final String PCB_GOODS_GO_MINDELIVERY_FIELD = "go_mindelivery";
+    public static final String PCB_GOODS_GO_UPDATE_DATE_FIELD = "go_update_date";
+    public static final String PCB_GOODS_CRNAME_FIELD = "cr_name";
+    public static final String PCB_GOODS_PCB_ID_FIELD = "pcb_id";
+    public static final String PCB_GOODS_KI_ID_FIELD = "ki_id";
+    public static final String PCB_GOODS_KI_NAME_CN_FIELD = "ki_name_cn";
+    public static final String PCB_GOODS_KI_NAME_CN_UNTOKENIZED_FIELD = "ki_name_cn_untokenized";
+    public static final String PCB_GOODS_KI_LEVEL_FIELD = "ki_level";
+    public static final String PCB_GOODS_KI_ISLEAF_FIELD = "ki_isleaf";
+    public static final String PCB_GOODS_BR_ID_FIELD = "br_id";
+    public static final String PCB_GOODS_BR_NAME_CN_FIELD = "br_name_cn";
+    public static final String PCB_GOODS_BR_NAME_EN_FIELD = "br_name_en";
+    public static final String PCB_GOODS_BR_NAME_CN_UNTOKENIZED_FIELD = "br_name_cn_untokenized";
+    public static final String PCB_GOODS_BR_NAME_EN_UNTOKENIZED_FIELD = "br_name_en_untokenized";
+    public static final String PCB_GOODS_BR_UUID_FIELD = "br_uuid";
+    public static final String PCB_GOODS_PR_ID_FIELD = "pr_id";
+    public static final String PCB_GOODS_PR_PCMPCODE_FIELD = "pr_pcmpcode";
+    // 自动生成的字段,用于唯一区分批次索引
+    public static final String PCB_GOODS_PRIMARY_KEY_FIELD = "primary_key";
+
 	// 商城销售订单索引字段的key
 	/**
 	 * 对应销售单id(数据库id字段)

+ 20 - 0
mall-search/src/main/java/com/uas/search/controller/IndexController.java

@@ -86,6 +86,26 @@ public class IndexController {
         return message;
     }
 
+    @RequestMapping("/multiDownloadPCB")
+    @ResponseBody
+    public String multiDownloadPCB(Integer threads, Integer startFileIndex, Integer endFileIndex, String validateResult, HttpServletRequest request) {
+        long start = System.currentTimeMillis();
+        String message= "Downloaded: "+ indexService.multiDownloadPCB(threads, startFileIndex, endFileIndex,
+                StringUtils.isEmpty(validateResult) ? DownloadHelper.ValidateResult.CURRENT : DownloadHelper.ValidateResult.valueOf(validateResult.toUpperCase()));
+        message += String.format(", Time: %.2fs", (System.currentTimeMillis()-start)/1000.0);
+        return message;
+    }
+
+    @RequestMapping("/multiDownloadPCBGoods")
+    @ResponseBody
+    public String multiDownloadPCBGoods(Integer threads, Integer startFileIndex, Integer endFileIndex, String validateResult, HttpServletRequest request) {
+        long start = System.currentTimeMillis();
+        String message= "Downloaded: "+ indexService.multiDownloadPCBGoods(threads, startFileIndex, endFileIndex,
+                StringUtils.isEmpty(validateResult) ? DownloadHelper.ValidateResult.CURRENT : DownloadHelper.ValidateResult.valueOf(validateResult.toUpperCase()));
+        message += String.format(", Time: %.2fs", (System.currentTimeMillis()-start)/1000.0);
+        return message;
+    }
+
     @RequestMapping("/listen/start")
     @ResponseBody
     public String startListen(Long interval, HttpServletRequest request) {

+ 35 - 4
mall-search/src/main/java/com/uas/search/controller/SearchController.java

@@ -6,10 +6,7 @@ import com.uas.search.constant.model.PageParams;
 import com.uas.search.constant.model.PageParams.FilterField;
 import com.uas.search.constant.model.SPage;
 import com.uas.search.dao.ComponentDao;
-import com.uas.search.model.Brand;
-import com.uas.search.model.Component;
-import com.uas.search.model.Goods;
-import com.uas.search.model.Kind;
+import com.uas.search.model.*;
 import com.uas.search.service.SearchService;
 import com.uas.search.service.impl.IndexServiceImpl;
 import com.uas.search.util.CollectionUtils;
@@ -213,6 +210,16 @@ public class SearchController {
         return goodsIds;
     }
 
+    @RequestMapping("/pcbGoodsIds")
+    @ResponseBody
+    public Map<String, Object> getPCBGoodsIds(String keyword, String params, HttpServletRequest request) throws IOException {
+        long start = System.currentTimeMillis();
+        PageParams pageParams = params == null ? null : JSONObject.parseObject(params, PageParams.class);
+        Map<String, Object> pcbGoodsIds = searchService.getPCBGoodsIds(keyword, pageParams);
+        logger.info(String.format("pcbGoodsIds\t%s\t%.3fs", keyword, (System.currentTimeMillis() - start) / 1000.0));
+        return pcbGoodsIds;
+    }
+
 	@RequestMapping("/collectBySearchGoods")
 	@ResponseBody
 	public List<Map<String, Object>> collectBySearchGoods(String keyword, @RequestParam String collectedField, String filters, HttpServletRequest request) throws IOException {
@@ -231,6 +238,24 @@ public class SearchController {
         return maps;
     }
 
+    @RequestMapping("/collectBySearchPCBGoods")
+    @ResponseBody
+    public List<Map<String, Object>> collectBySearchPCBGoods(String keyword, @RequestParam String collectedField, String filters, HttpServletRequest request) throws IOException {
+        long start = System.currentTimeMillis();
+        Map<FilterField, Object> filtersMap = new HashMap<>();
+        if(!StringUtils.isEmpty(filters)){
+            JSONObject json = JSONObject.parseObject(filters);
+            Set<Entry<String, Object>> entrySet = json.entrySet();
+            for (Entry<String, Object> entry : entrySet) {
+                filtersMap.put(FilterField.valueOf(entry.getKey().toUpperCase()), entry.getValue());
+            }
+        }
+        List<Map<String, Object>> maps = searchService.collectBySearchPCBGoods(keyword, CollectField.valueOf(collectedField.toUpperCase()),
+                filtersMap);
+        logger.info(String.format("collect\t%s\t%.3fs", keyword, (System.currentTimeMillis()-start)/1000.0));
+        return maps;
+    }
+
 	@RequestMapping("/kind/{id}")
 	@ResponseBody
 	public Kind getKind(@PathVariable Long id, HttpServletRequest request) throws IOException {
@@ -255,6 +280,12 @@ public class SearchController {
 		return searchService.getGoods(id);
 	}
 
+    @RequestMapping("/pcbGoods/{id}")
+    @ResponseBody
+    public PCBGoods getPCBGoods(@PathVariable String id, HttpServletRequest request) throws IOException {
+        return searchService.getPCBGoods(id);
+    }
+
 	@RequestMapping("/objects")
 	@ResponseBody
 	public SPage<Object> getObjects(@RequestParam String tableName, String keyword, String field,

+ 18 - 0
mall-search/src/main/java/com/uas/search/dao/PCBDao.java

@@ -0,0 +1,18 @@
+package com.uas.search.dao;
+
+import com.uas.search.model.PCB;
+import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
+import org.springframework.stereotype.Repository;
+
+/**
+ * PCB
+ *
+ * @author sunyj
+ * @since 2018/4/26 9:09
+ */
+@Repository
+public interface PCBDao extends JpaSpecificationExecutor<PCB>, JpaRepository<PCB, Long> {
+
+    PCB findByProducts_Id(Long productsId);
+}

+ 139 - 0
mall-search/src/main/java/com/uas/search/dao/PCBGoodsDao.java

@@ -0,0 +1,139 @@
+package com.uas.search.dao;
+
+import com.uas.search.exception.DataNotFoundException;
+import com.uas.search.model.PCB;
+import com.uas.search.model.PCBGoods;
+import com.uas.search.model.TradeGoods;
+import com.uas.search.util.CollectionUtils;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.stereotype.Repository;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * PCB 批次
+ *
+ * @author sunyj
+ * @since 2018/4/26 9:09
+ */
+@Repository
+public class PCBGoodsDao {
+
+    @Autowired
+    private TradeGoodsDao tradeGoodsDao;
+
+    @Autowired
+    private PCBDao pcbDao;
+
+    /**
+     * 根据批次 id 或者 PCB id 获取批次
+     *
+     * @param pcbGoods 批次 id 或者 PCB id
+     * @return 批次
+     */
+    public List<PCBGoods> find(PCBGoods pcbGoods) {
+        if (pcbGoods == null) {
+            return null;
+        }
+        List<PCBGoods> goodsesList = new ArrayList<>();
+        if (pcbGoods.getPcb() != null && pcbGoods.getPcb().getId() != null) {
+            goodsesList.addAll(findByPCBId(pcbGoods.getPcb().getId()));
+        } else if (pcbGoods.getTradeGoods() != null && pcbGoods.getTradeGoods().getId() != null) {
+            goodsesList.add(findByGoId(pcbGoods.getTradeGoods().getId()));
+        }
+        return goodsesList;
+    }
+
+    /**
+     * 根据批次 id 获取批次信息
+     *
+     * @param goId 批次 id
+     * @return 批次信息
+     */
+    private PCBGoods findByGoId(Long goId) {
+        TradeGoods tradeGoods = tradeGoodsDao.findOne(goId);
+        if (tradeGoods == null) {
+            throw new DataNotFoundException("批次不存在:" + goId);
+        }
+        return findByTradeGoods(tradeGoods);
+    }
+
+    /**
+     * 获取批次信息
+     *
+     * @param tradeGoods 批次
+     * @return 批次信息
+     */
+    public PCBGoods findByTradeGoods(TradeGoods tradeGoods) {
+        if (tradeGoods.getProductId() != null) {
+            PCB pcb = pcbDao.findByProducts_Id(tradeGoods.getProductId());
+            if (pcb != null) {
+                return new PCBGoods(tradeGoods, pcb);
+            }
+        }
+        return null;
+    }
+
+    /**
+     * 根据 PCB id 获取批次信息
+     *
+     * @param pcbId PCB id
+     * @return 批次信息
+     */
+    public List<PCBGoods> findByPCBId(Long pcbId) {
+        PCB pcb = pcbDao.findOne(pcbId);
+        if (pcb == null) {
+            throw new DataNotFoundException("PCB 不存在:" + pcbId);
+        }
+        return findByPCB(pcb);
+    }
+
+    /**
+     * 根据 PCB 获取批次信息
+     *
+     * @param pcb PCB
+     * @return 批次信息
+     */
+    public List<PCBGoods> findByPCB(PCB pcb) {
+        if (pcb == null || pcb.getProducts() == null || pcb.getProducts().getId() == null) {
+            return null;
+        }
+        List<PCBGoods> pcbGoodsesList = new ArrayList<>();
+        List<TradeGoods> tradeGoodsesList = tradeGoodsDao.findByProductId(pcb.getProducts().getId());
+        boolean hasValidGoods = false;
+        if (!CollectionUtils.isEmpty(tradeGoodsesList)) {
+            for (TradeGoods tradeGoods : tradeGoodsesList) {
+                // 判断 PCB 下是否有状态有效的批次
+                if (!hasValidGoods && isValidStatus(tradeGoods.getStatus())) {
+                    hasValidGoods = true;
+                }
+                pcbGoodsesList.add(new PCBGoods(tradeGoods, pcb));
+            }
+        }
+
+        //  PCB 下没有状态有效的批次(可能没有批次,或者批次状态全都无效)时,另外保存一份不含批次信息的 PCB 数据
+        if (!hasValidGoods) {
+            pcbGoodsesList.add(new PCBGoods(null, pcb));
+        }
+        return pcbGoodsesList;
+    }
+
+    /**
+     * 是否为有效状态
+     *
+     * @param status 状态
+     * @return true 有效;false 无效
+     */
+    private boolean isValidStatus(Long status) {
+        if (status == null) {
+            return false;
+        }
+        for (Long validStatus : TradeGoods.VALID_STATUS) {
+            if (validStatus.longValue() == status.longValue()) {
+                return true;
+            }
+        }
+        return false;
+    }
+}

+ 6 - 5
mall-search/src/main/java/com/uas/search/dao/TradeGoodsDao.java

@@ -1,12 +1,11 @@
 package com.uas.search.dao;
 
-import java.util.List;
-
+import com.uas.search.model.TradeGoods;
 import org.springframework.data.jpa.repository.JpaRepository;
 import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
 import org.springframework.stereotype.Repository;
 
-import com.uas.search.model.TradeGoods;
+import java.util.List;
 
 /**
  * @author sunyj
@@ -14,7 +13,9 @@ import com.uas.search.model.TradeGoods;
  */
 @Repository
 public interface TradeGoodsDao
-		extends JpaSpecificationExecutor<TradeGoods>, JpaRepository<TradeGoods, Long> {
+        extends JpaSpecificationExecutor<TradeGoods>, JpaRepository<TradeGoods, Long> {
+
+    List<TradeGoods> findByCmpUuid(String cmpUuid);
 
-	public List<TradeGoods> findByCmpUuid(String cmpUuid);
+    List<TradeGoods> findByProductId(Long productId);
 }

+ 27 - 1
mall-search/src/main/java/com/uas/search/jms/QueueMessageParser.java

@@ -72,7 +72,9 @@ public class QueueMessageParser {
 				object = parseComponent(dataId, methodType);
 			} else if (tableName.equals(SearchConstants.GOODS_TABLE_NAME)) {
 				object = parseGoods(dataId, data);
-			} else if (tableName.equals(SearchConstants.ORDER_TABLE_NAME)) {
+			} else if (tableName.equals(SearchConstants.PCB_GOODS_TABLE_NAME)) {
+                object = parsePCBGoods(dataId, data);
+            } else if (tableName.equals(SearchConstants.ORDER_TABLE_NAME)) {
 				object = parseOrder(dataId, methodType);
 			} else if (tableName.equals(SearchConstants.ORDER_INVOICE_TABLE_NAME)) {
 				object = parseOrderInvoice(dataId, methodType);
@@ -175,6 +177,30 @@ public class QueueMessageParser {
 		return goods;
 	}
 
+    /**
+     * 对 PCB 批次 进行解析
+     *
+     * @param id id
+     * @param data 数据
+     * @return PCB 批次对象
+     * @throws JSONException
+     */
+    private PCBGoods parsePCBGoods(@NotEmpty("id") Long id, @NotEmpty("data") String data) throws JSONException {
+        PCBGoods goods = new PCBGoods();
+        if (data == null || data.equalsIgnoreCase("pcbId")) {
+            PCB pcb = new PCB();
+            pcb.setId(id);
+            goods.setPcb(pcb);
+        } else if (data.equalsIgnoreCase("goId")){
+            TradeGoods tradeGoods = new TradeGoods();
+            tradeGoods.setId(id);
+            goods.setTradeGoods(tradeGoods);
+        } else {
+            throw new IllegalArgumentException("未指定是 pcbId 还是 goId");
+        }
+        return goods;
+    }
+
 	/**
 	 * 对销售单进行解析
 	 *

+ 87 - 0
mall-search/src/main/java/com/uas/search/model/PCB.java

@@ -0,0 +1,87 @@
+package com.uas.search.model;
+
+import javax.persistence.*;
+import java.io.Serializable;
+
+/**
+ * PCB
+ *
+ * @author sunyj
+ * @since 2018/4/25 17:24
+ */
+@Entity
+@Table(name = "product$pcb")
+public class PCB implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * id
+     */
+    @Id
+    @Column(name = "pcb_id")
+    private Long id;
+
+    /**
+     * 品牌
+     */
+    @ManyToOne
+    @JoinColumn(name = "pcb_brandid", insertable = false, updatable = false)
+    private Brand brand;
+
+    /**
+     * 类目
+     */
+    @ManyToOne
+    @JoinColumn(name = "pcb_kindid", insertable = false, updatable = false)
+    private Kind kind;
+
+    /**
+     * 物料
+     */
+    @OneToOne
+    @JoinColumn(name = "pcb_productid", insertable = false, updatable = false)
+    private Products products;
+
+    public Long getId() {
+        return id;
+    }
+
+    public void setId(Long id) {
+        this.id = id;
+    }
+
+    public Brand getBrand() {
+        return brand;
+    }
+
+    public void setBrand(Brand brand) {
+        this.brand = brand;
+    }
+
+    public Kind getKind() {
+        return kind;
+    }
+
+    public void setKind(Kind kind) {
+        this.kind = kind;
+    }
+
+    public Products getProducts() {
+        return products;
+    }
+
+    public void setProducts(Products products) {
+        this.products = products;
+    }
+
+    @Override
+    public String toString() {
+        return "PCB{" +
+                "id=" + id +
+                ", brand=" + brand +
+                ", kind=" + kind +
+                ", products=" + products +
+                '}';
+    }
+}

+ 56 - 0
mall-search/src/main/java/com/uas/search/model/PCBGoods.java

@@ -0,0 +1,56 @@
+package com.uas.search.model;
+
+import java.io.Serializable;
+
+/**
+ * PCB 批次
+ *
+ * @author sunyj
+ * @since 2018/4/26 9:06
+ */
+public class PCBGoods implements Serializable {
+
+    private static final long serialVersionUID = 1L;
+
+    /**
+     * 批次
+     */
+    private TradeGoods tradeGoods;
+
+    /**
+     * PCB
+     */
+    private PCB pcb;
+
+    public PCBGoods() {
+    }
+
+    public PCBGoods(TradeGoods tradeGoods, PCB pcb) {
+        this.tradeGoods = tradeGoods;
+        this.pcb = pcb;
+    }
+
+    public TradeGoods getTradeGoods() {
+        return tradeGoods;
+    }
+
+    public void setTradeGoods(TradeGoods tradeGoods) {
+        this.tradeGoods = tradeGoods;
+    }
+
+    public PCB getPcb() {
+        return pcb;
+    }
+
+    public void setPcb(PCB pcb) {
+        this.pcb = pcb;
+    }
+
+    @Override
+    public String toString() {
+        return "PCBGoods{" +
+                "tradeGoods=" + tradeGoods +
+                ", pcb=" + pcb +
+                '}';
+    }
+}

+ 23 - 0
mall-search/src/main/java/com/uas/search/service/IndexService.java

@@ -47,6 +47,29 @@ public interface IndexService {
      */
     public long multiDownloadGoods(Integer threads, Integer startFileIndex, Integer endFileIndex, DownloadHelper.ValidateResult validateResult);
 
+    /**
+     * 多线程下载 PCB 的数据至本地文件中,以供建索引用
+     *
+     * @param threads        线程数量,默认为 1
+     * @param startFileIndex 开始的文件,默认为 1
+     * @param endFileIndex   结束的文件,默认为 1024 * 1024 * 1024
+     * @param validateResult 下载完成后,是否对结果进行校验
+     * @return 下载的数据条数
+     */
+    long multiDownloadPCB(Integer threads, Integer startFileIndex, Integer endFileIndex, DownloadHelper.ValidateResult validateResult);
+
+    /**
+     * 多线程下载 PCB 批次的数据至本地文件中,以供建索引用
+     *
+     * @param threads        线程数量,默认为 1
+     * @param startFileIndex 开始的文件,默认为 1
+     * @param endFileIndex   结束的文件,默认为 1024 * 1024 * 1024
+     * @param validateResult 下载完成后,是否对结果进行校验
+     * @return 下载的数据条数
+     */
+    long multiDownloadPCBGoods(Integer threads, Integer startFileIndex, Integer endFileIndex, DownloadHelper.ValidateResult validateResult);
+
+
     /**
      * 将新对象添加在lucene索引中
      *

+ 117 - 4
mall-search/src/main/java/com/uas/search/service/SearchService.java

@@ -5,10 +5,7 @@ import com.uas.search.constant.model.CollectField;
 import com.uas.search.constant.model.PageParams;
 import com.uas.search.constant.model.PageParams.FilterField;
 import com.uas.search.constant.model.SPage;
-import com.uas.search.model.Brand;
-import com.uas.search.model.Component;
-import com.uas.search.model.Goods;
-import com.uas.search.model.Kind;
+import com.uas.search.model.*;
 
 import java.io.IOException;
 import java.util.List;
@@ -295,6 +292,59 @@ public interface SearchService {
 	 */
 	public Map<String, Object> getGoodsIds(String keyword, PageParams pageParams) throws IOException;
 
+    /**
+     * 根据关键词搜索 PCB 批次
+     *
+     * @param keyword
+     *            关键词
+     * @param pageParams
+     *            翻页、过滤、排序等信息
+     *            <p>
+     *            关于过滤,通过键值对指定过滤条件,键为
+     *            {@link com.uas.search.constant.model.PageParams.FilterField}
+     *            ,值的类型由键决定:
+     *            </p>
+     *
+     *            <table border=1 cellpadding=5 cellspacing=0 summary=
+     *            "Fields and types">
+     *            <tr>
+     *            <th>Field</th>
+     *            <th>Type</th>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_KINDID</td>
+     *            <td>List(Long)</td>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_BRANDID</td>
+     *            <td>List(Long)</td>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_CRNAME</td>
+     *            <td>List(String)</td>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_MINPRICERMB</td>
+     *            <td>Double</td>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_MAXPRICERMB</td>
+     *            <td>Double</td>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_MINPRICEUSD</td>
+     *            <td>Double</td>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_MAXPRICEUSD</td>
+     *            <td>Double</td>
+     *            </tr>
+     *            </table>
+     *
+     * @return PCB id、批次 id 和分页信息
+     */
+    Map<String, Object> getPCBGoodsIds(String keyword, PageParams pageParams) throws IOException;
+
 	/**
 	 * 搜索批次时,统计指定信息
 	 * 
@@ -358,6 +408,61 @@ public interface SearchService {
 	public List<Map<String, Object>> collectBySearchGoods(String keyword, CollectField collectedField,
 			Map<FilterField, Object> filters) throws IOException;
 
+    /**
+     * 搜索 PCB 批次时,统计指定信息
+     *
+     * @param keyword
+     *            关键词
+     * @param collectedField
+     *            需要统计的信息
+     * @param filters
+     *            过滤条件,值的类型由键决定:
+     *
+     *            <table border=1 cellpadding=5 cellspacing=0 summary=
+     *            "Fields and types">
+     *            <tr>
+     *            <th>Field</th>
+     *            <th>Type</th>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_KINDID</td>
+     *            <td>List(Long)</td>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_BRANDID</td>
+     *            <td>List(Long)</td>
+     *            </tr>
+     *            <tr>
+     *            <td>GOODS_CRNAME</td>
+     *            <td>List(String)</td>
+     *            </tr>
+     *            </table>
+     *
+     * @return 统计的信息(由collectedField决定)
+     *
+     *         <table border=1 cellpadding=5 cellspacing=0 summary=
+     *         "Collected fields and messages">
+     *         <tr>
+     *         <th>Collected field</th>
+     *         <th>Message</th>
+     *         </tr>
+     *         <tr>
+     *         <td>GOODS_KIND</td>
+     *         <td>ki_id、ki_name_cn</td>
+     *         </tr>
+     *         <tr>
+     *         <td>GOODS_BRAND</td>
+     *         <td>br_id、br_uuid、br_name_cn、br_name_en</td>
+     *         </tr>
+     *         <tr>
+     *         <td>GOODS_CRNAME</td>
+     *         <td>cr_name</td>
+     *         </tr>
+     *         </table>
+     */
+    List<Map<String, Object>> collectBySearchPCBGoods(String keyword, CollectField collectedField,
+                                                   Map<FilterField, Object> filters) throws IOException;
+
 	/**
 	 * 根据id获取类目
 	 * 
@@ -390,6 +495,14 @@ public interface SearchService {
 	 */
 	public Goods getGoods(String id) throws IOException;
 
+    /**
+     * 根据id获取 PCB 批次
+     *
+     * @param id
+     * @return
+     */
+    public PCBGoods getPCBGoods(String id) throws IOException;
+
 	/**
 	 * 分页获取本地指定表的索引中的数据
 	 * 

+ 236 - 4
mall-search/src/main/java/com/uas/search/service/impl/IndexServiceImpl.java

@@ -37,8 +37,7 @@ import org.springframework.stereotype.Service;
 import java.io.*;
 import java.util.*;
 
-import static com.uas.search.constant.SearchConstants.COMPONENT_TABLE_NAME;
-import static com.uas.search.constant.SearchConstants.GOODS_TABLE_NAME;
+import static com.uas.search.constant.SearchConstants.*;
 
 /**
  * 创建索引
@@ -74,6 +73,10 @@ public class IndexServiceImpl implements IndexService {
     @Autowired
     private GoodsDao goodsDao;
     @Autowired
+    private PCBDao pcbDao;
+    @Autowired
+    private PCBGoodsDao pcbGoodsDao;
+    @Autowired
     private OrderDao orderDao;
     @Autowired
     private OrderInvoiceDao orderInvoiceDao;
@@ -129,6 +132,8 @@ public class IndexServiceImpl implements IndexService {
                         }
                     } else if (tableName.equals(SearchConstants.GOODS_TABLE_NAME)) {
                         size = createGoodsIndexesFromFiles();
+                    } else if (tableName.equals(PCB_GOODS_TABLE_NAME)) {
+                        size = createPCBGoodsIndexesFromFiles();
                     } else if (tableName.equals(SearchConstants.ORDER_TABLE_NAME)) {
                         size = createOrderIndexes();
                     } else if (tableName.equals(SearchConstants.ORDER_INVOICE_TABLE_NAME)) {
@@ -445,6 +450,157 @@ public class IndexServiceImpl implements IndexService {
         }
     }
 
+    private Long createPCBGoodsIndexesFromFiles() throws IOException {
+        return createPCBGoodsIndexesFromFiles(true);
+    }
+
+    /**
+     * 创建 PCB 批次索引
+     *
+     * @param updateFromDbAfterCreating 创建索引后是否从批次表获取数据,更新本地索引(此种情况下,用于创建索引的数据来自器件,并不包括批次,因此需要单独进行更新)
+     * @return 写入的索引总数(不包括创建之后,所更新的数目)
+     * @throws IOException
+     */
+    private Long createPCBGoodsIndexesFromFiles(boolean updateFromDbAfterCreating) throws IOException {
+        if (updateFromDbAfterCreating) {
+            logger.info("正在创建 PCB 批次索引...");
+        } else {
+            logger.info("正在更新 PCB 批次索引...");
+        }
+        Long size = 0L;
+        BufferedReader bufferedReader = null;
+        try {
+            // 如果创建后需要更新本地索引,说明还未创建索引,本次便是在进行创建索引,需要转换 PCB 数据,用来建索引
+            if (updateFromDbAfterCreating) {
+                convertPCBGoodsFromPCB();
+            }
+            // 从本地路径读取批次数据
+            File[] files = new File(SearchUtils.getDataPath(PCB_GOODS_TABLE_NAME)).listFiles();
+            if (files == null || files.length == 0) {
+                logger.info("创建 PCB 批次索引失败,原因:PCB 批次数据文件不存在!");
+                return 0L;
+            }
+            for (File file : files) {
+                logger.info("读取文件: " + file.getName());
+                bufferedReader = new BufferedReader(new FileReader(file));
+                String line;
+                while (!StringUtils.isEmpty(line = bufferedReader.readLine())) {
+                    PCBGoods pcbGoods;
+                    try {
+                        pcbGoods = JSONObject.parseObject(line, PCBGoods.class);
+                    } catch (JSONException e) {
+                        throw new IllegalArgumentException(line, e);
+                    }
+                    // 如果创建后不需要更新本地索引,说明已经创建了索引,本次便是在进行更新索引
+                    if (!updateFromDbAfterCreating) {
+                        indexWriterManager.release(PCB_GOODS_TABLE_NAME);
+                        maintainIndexes(new ParsedQueueMessage(ParsedQueueMessage.UPDATE, pcbGoods));
+                        try {
+                            indexWriter = indexWriterManager.get(PCB_GOODS_TABLE_NAME);
+                        } catch (InterruptedException e) {
+                            logger.error("", e);
+                        }
+                    } else {
+                        Document document = ObjectToDocumentUtils.toDocument(pcbGoods);
+                        if (document != null) {
+                            size++;
+                            // 每创建10000条,打印一次进度
+                            if (size % 10000 == 0) {
+                                logger.info("PCB goods indexed..................." + size);
+                            }
+                            indexWriter.addDocument(document);
+                        }
+                    }
+                }
+                indexWriter.commit();
+                bufferedReader.close();
+            }
+        } catch (FileNotFoundException e) {
+            logger.error("创建 PCB 批次索引失败,原因:PCB 批次数据文件不存在!");
+            return 0L;
+        } finally {
+            indexWriter.commit();
+            if (bufferedReader != null) {
+                bufferedReader.close();
+            }
+        }
+        // 如果需要从批次表获取数据,更新本地索引
+        if (updateFromDbAfterCreating) {
+            try {
+                multiDownloadPCBGoods(null, null, null, DownloadHelper.ValidateResult.CURRENT);
+                createPCBGoodsIndexesFromFiles(false);
+            } catch (Throwable e) {
+                throw new IllegalStateException("PCB 批次索引建立完成后,获取批次表数据用来更新索引时出错", e);
+            }
+        }
+        return size;
+    }
+
+    /**
+     * 根据本地 PCB 的数据文件,转为 PCB 批次数据
+     *
+     * @return 花费总时间 ms
+     */
+    private Long convertPCBGoodsFromPCB() {
+        Long startTime = new Date().getTime();
+        Long size = 0L;
+        logger.info("转换 PCB 批次... ");
+        BufferedReader bufferedReader = null;
+        try {
+            // 从本地路径读取 PCB 数据
+            String pcbDataPath = SearchUtils.getDataPath(PCB_TABLE_NAME);
+            String pcbGoodsDataPath = SearchUtils.getDataPath(PCB_GOODS_TABLE_NAME);
+            File[] files = new File(pcbDataPath).listFiles();
+            if (files == null || files.length == 0) {
+                logger.info("转换 PCB 批次失败,原因:PCB 数据文件不存在!");
+                return 0L;
+            }
+            File pcbGgoodsDataDir = new File(pcbGoodsDataPath);
+            if (!pcbGgoodsDataDir.exists()) {
+                pcbGgoodsDataDir.mkdirs();
+            } else {
+                FileUtils.deleteSubFiles(pcbGgoodsDataDir);
+            }
+            int fileIndex = 1;
+            for (File file : files) {
+                logger.info("读取 PCB 文件: " + file.getName());
+                bufferedReader = new BufferedReader(new FileReader(file));
+                String pcbGoodsFileName = String.format("%010d", fileIndex) + ".txt";
+                PrintWriter printWriter = new PrintWriter(pcbGoodsDataPath + "/" + pcbGoodsFileName);
+                String line;
+                while (!StringUtils.isEmpty(line = bufferedReader.readLine())) {
+                    PCB pcb;
+                    try {
+                        pcb = JSONObject.parseObject(line, PCB.class);
+                    } catch (JSONException e) {
+                        throw new IllegalArgumentException(line, e);
+                    }
+                    // PCB 作为主体,得到批次
+                    printWriter.println(JSONObject.toJSONString(new PCBGoods(null, pcb)));
+                    size++;
+                }
+                logger.info(pcbGoodsFileName + " - Converted..................." + size);
+                printWriter.flush();
+                printWriter.close();
+                fileIndex++;
+                bufferedReader.close();
+            }
+            long endStartTime = new Date().getTime();
+            logger.info(String.format("转换完成,耗时%.2fs\n ", (endStartTime - startTime) / 1000.0));
+            return endStartTime - startTime;
+        } catch (Throwable e) {
+            throw new IllegalStateException("PCB 批次转换失败", e);
+        } finally {
+            if (bufferedReader != null) {
+                try {
+                    bufferedReader.close();
+                } catch (IOException e) {
+                    throw new IllegalStateException("PCB 批次转换失败", e);
+                }
+            }
+        }
+    }
+
     private Long createOrderIndexes() {
         logger.info("正在创建销售单索引...");
         List<Order> orders = orderDao.findAll();
@@ -511,6 +667,16 @@ public class IndexServiceImpl implements IndexService {
         return multiDownloadData(GOODS_TABLE_NAME, threads, startFileIndex, endFileIndex, validateResult);
     }
 
+    @Override
+    public long multiDownloadPCB(Integer threads, Integer startFileIndex, Integer endFileIndex, DownloadHelper.ValidateResult validateResult) {
+        return multiDownloadData(PCB_TABLE_NAME, threads, startFileIndex, endFileIndex, validateResult);
+    }
+
+    @Override
+    public long multiDownloadPCBGoods(Integer threads, Integer startFileIndex, Integer endFileIndex, DownloadHelper.ValidateResult validateResult) {
+        return multiDownloadData(PCB_GOODS_TABLE_NAME, threads, startFileIndex, endFileIndex, validateResult);
+    }
+
     /**
      * 多线程下载数据
      *
@@ -540,6 +706,19 @@ public class IndexServiceImpl implements IndexService {
                 }
             }, validateResult);
             return downloadHelper.getResult();
+        } else if (tableName.equals(PCB_TABLE_NAME)) {
+            DownloadHelper<PCB> downloadHelper = new DownloadHelper<>(threads, startFileIndex, endFileIndex, tableName, "id", pcbDao, new DownloadService<PCB>(), validateResult);
+            return downloadHelper.getResult();
+
+        } else if (tableName.equals(PCB_GOODS_TABLE_NAME)) {
+            DownloadHelper<TradeGoods> downloadHelper = new DownloadHelper<>(threads, startFileIndex, endFileIndex, tableName, "id", tradeGoodsDao, new DownloadService<TradeGoods>() {
+                @Override
+                protected void println(PrintWriter printWriter, TradeGoods element) {
+                    PCBGoods pcbGoods = pcbGoodsDao.findByTradeGoods(element);
+                    printWriter.println(JSONObject.toJSONString(pcbGoods));
+                }
+            }, validateResult);
+            return downloadHelper.getResult();
         } else {
             throw new IllegalArgumentException("多线程下载不支持该表:" + tableName);
         }
@@ -584,7 +763,7 @@ public class IndexServiceImpl implements IndexService {
                         Brand brand = (Brand) obj;
                         indexWriter.updateDocument(new Term(SearchConstants.BRAND_ID_FIELD,
                                 String.valueOf(brand.getId())), document);
-                        // 更新关联批次
+                        // 更新关联批次(更新品牌权重)
                         updateGoodsFields(new Term(SearchConstants.GOODS_BR_ID_FIELD, brand.getId().toString()), brand, null);
                     } else if (obj instanceof Component) {
                         indexWriter.updateDocument(new Term(SearchConstants.COMPONENT_ID_FIELD,
@@ -687,6 +866,8 @@ public class IndexServiceImpl implements IndexService {
                             String.valueOf(((Component) obj).getId())));
                 } else if (obj instanceof Goods) {
                     indexWriter.deleteDocuments(toTerm((Goods) obj));
+                } else if (obj instanceof PCBGoods) {
+                    indexWriter.deleteDocuments(toTerm((PCBGoods) obj));
                 } else if (obj instanceof Order) {
                     indexWriter.deleteDocuments(
                             new Term(SearchConstants.ORDER_ID_FIELD, String.valueOf(((Order) obj).getId())));
@@ -728,6 +909,21 @@ public class IndexServiceImpl implements IndexService {
         return null;
     }
 
+    /**
+     * 根据 PCBGoods 构造Term
+     *
+     * @param goods
+     * @return
+     */
+    private Term toTerm(PCBGoods goods) {
+        if (goods.getPcb() != null) {
+            return new Term(SearchConstants.PCB_GOODS_PCB_ID_FIELD, String.valueOf(goods.getPcb().getId()));
+        } else if (goods.getTradeGoods() != null) {
+            return new Term(SearchConstants.PCB_GOODS_GO_ID_FIELD, String.valueOf(goods.getTradeGoods().getId()));
+        }
+        return null;
+    }
+
     @Override
     public List<Object> maintainIndexes(@NotEmpty("parsedQueueMessage") ParsedQueueMessage parsedQueueMessage) {
         Object object = parsedQueueMessage.getObject();
@@ -747,6 +943,16 @@ public class IndexServiceImpl implements IndexService {
                         maintainedObjects.add(maintainedObject);
                     }
                 }
+            } else if (object instanceof PCBGoods) {
+                // 先删除相关 PCB 批次和器件,再重新写入
+                delete(object);
+                List<PCBGoods> pcbGoodsesList = pcbGoodsDao.find((PCBGoods) object);
+                for (PCBGoods pcbGoods : pcbGoodsesList) {
+                    Object maintainedObject = save(pcbGoods);
+                    if (maintainedObject != null) {
+                        maintainedObjects.add(maintainedObject);
+                    }
+                }
             } else {
                 Object maintainedObject = update(object);
                 if (maintainedObject != null) {
@@ -775,6 +981,25 @@ public class IndexServiceImpl implements IndexService {
                         logger.warn("器件已删除,不必重新写入相关批次和器件", e);
                     }
                 }
+            } else if (object instanceof PCBGoods) {
+                // 先删除相关批次和 PCB,再重新写入
+                delete(object);
+                PCBGoods pcbGoodsObject = (PCBGoods) object;
+                if (pcbGoodsObject.getPcb() != null && pcbGoodsObject.getPcb().getId() != null) {
+                    try {
+                        // 如果是 PCB,再重新写入
+                        List<PCBGoods> pcbGoodsesList = pcbGoodsDao.find((PCBGoods) object);
+                        for (PCBGoods pcbGoods : pcbGoodsesList) {
+                            Object maintainedObject = save(pcbGoods);
+                            if (maintainedObject != null) {
+                                maintainedObjects.add(maintainedObject);
+                            }
+                        }
+                    } catch (DataNotFoundException e) {
+                        // 删除操作时,PCB 可能已经不存在,此时是正常情况,不必抛异常
+                        logger.warn("PCB 已删除,不必重新写入相关批次和 PCB", e);
+                    }
+                }
             } else {
                 Object maintainedObject = delete(object);
                 if (maintainedObject != null) {
@@ -813,7 +1038,14 @@ public class IndexServiceImpl implements IndexService {
         updatedObjects.put(SearchConstants.GOODS_TABLE_NAME, updateIndexByNewWords(newWords,
                 SearchConstants.GOODS_TABLE_NAME, SearchConstants.GOODS_CMP_ID_FIELD,
                 SearchConstants.GOODS_CMP_CODE_FIELD, SearchConstants.GOODS_KI_NAME_CN_FIELD,
-                SearchConstants.GOODS_BR_NAME_CN_UNTOKENIZED_FIELD, SearchConstants.GOODS_BR_NAME_EN_UNTOKENIZED_FIELD));
+                SearchConstants.GOODS_BR_NAME_CN_FIELD, SearchConstants.GOODS_BR_NAME_EN_FIELD));
+
+        // PCB 批次
+        updatedObjects.put(SearchConstants.PCB_GOODS_TABLE_NAME, updateIndexByNewWords(newWords,
+                SearchConstants.PCB_GOODS_TABLE_NAME, SearchConstants.PCB_GOODS_PCB_ID_FIELD,
+                SearchConstants.PCB_GOODS_PR_PCMPCODE_FIELD, SearchConstants.PCB_GOODS_KI_NAME_CN_FIELD,
+                SearchConstants.PCB_GOODS_BR_NAME_CN_FIELD, SearchConstants.PCB_GOODS_BR_NAME_EN_FIELD));
+
         return updatedObjects;
     }
 

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

@@ -1073,6 +1073,230 @@ public class SearchServiceImpl implements SearchService {
         return new Sort(sortFields);
     }
 
+    @Override
+    public Map<String, Object> getPCBGoodsIds(String keyword, PageParams pageParams) throws IOException {
+        keyword = recursivelyGetPCBGoodsIds(keyword);
+        // 因为器件、属性值等的数据量远比类目、品牌大得多,而且器件搜索可能还需进行分页,
+        // 所以涉及器件、属性值的搜索,大都不能像类目和品牌一样直接利用SearchUtils.getDocuments方法
+        IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.PCB_GOODS_TABLE_NAME);
+
+        if (pageParams == null) {
+            pageParams = new PageParams();
+        }
+        if (pageParams.getPage() <= 0)
+            pageParams.setPage(1);
+        if (pageParams.getSize() <= 0)
+            pageParams.setSize(20);
+
+        Map<String, Object> map = new HashMap<String, Object>();
+        List<Long> pcbIds = new ArrayList<>();
+        List<Long> goIds = new ArrayList<>();
+        try {
+            BooleanQuery booleanQuery = queryPCBGoods(keyword);
+            setPCBGoodsFilter(pageParams.getFilters(), booleanQuery);
+            logger.info(booleanQuery.toString());
+
+            Sort sort = sortPCBGoods(keyword, pageParams.getSort());
+            TopDocs hits;
+            if (pageParams.getPage() > 1) {// 不是第一页
+                TopDocs previousHits = indexSearcher.search(booleanQuery,
+                        (pageParams.getPage() - 1) * pageParams.getSize(), sort, true, false);
+                int totalHits = previousHits.totalHits;
+                if ((pageParams.getPage() - 1) * pageParams.getSize() >= totalHits) {
+                    return map;
+                }
+                ScoreDoc[] previousScoreDocs = previousHits.scoreDocs;
+                ScoreDoc after = previousScoreDocs[previousScoreDocs.length - 1];
+                hits = indexSearcher.searchAfter(after, booleanQuery, pageParams.getSize(), sort, true, false);
+            } else {
+                hits = indexSearcher.search(booleanQuery, pageParams.getSize(), sort, true, false);
+            }
+
+            // 数据量太大,需要指定将获取的数据(以免载入不必要的数据,降低速度)
+            Set<String> fieldsToLoad = new HashSet<>();
+            fieldsToLoad.add(SearchConstants.PCB_GOODS_PCB_ID_FIELD);
+            fieldsToLoad.add(SearchConstants.PCB_GOODS_GO_ID_FIELD);
+            ScoreDoc[] scoreDocs = hits.scoreDocs;
+            for (ScoreDoc scoreDoc : scoreDocs) {
+                Document document = indexSearcher.doc(scoreDoc.doc, fieldsToLoad);
+                String pcbId = document.get(SearchConstants.PCB_GOODS_PCB_ID_FIELD);
+                pcbIds.add(StringUtils.isEmpty(pcbId) || pcbId.equals(ObjectToDocumentUtils.NULL_VALUE) ? null : Long.valueOf(pcbId));
+                String goId = document.get(SearchConstants.PCB_GOODS_GO_ID_FIELD);
+                goIds.add(StringUtils.isEmpty(goId) || goId.equals(ObjectToDocumentUtils.NULL_VALUE) ? null : Long.valueOf(goId));
+            }
+            map.put("pcbIds", pcbIds);
+            map.put("goodsIds", goIds);
+            map.put("page", pageParams.getPage());
+            map.put("size", pageParams.getSize());
+            map.put("total", hits.totalHits);
+        } catch (IOException e) {
+            logger.error("", e);
+        } finally {
+            SearchUtils.releaseIndexSearcher(indexSearcher);
+        }
+        return map;
+    }
+
+
+    /**
+     * 递归查询 PCB 批次(如果没有结果,则降低精度,直至长度为 1)
+     *
+     * @param keyword       关键词
+     * @return 最后一次搜索的关键词
+     */
+    private String recursivelyGetPCBGoodsIds(String keyword) throws IOException {
+        IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.PCB_GOODS_TABLE_NAME);
+        try {
+            BooleanQuery booleanQuery = queryPCBGoods(keyword);
+            logger.info(booleanQuery.toString());
+            TotalHitCountCollector collector = new TotalHitCountCollector();
+            indexSearcher.search(booleanQuery, collector);
+            // 如果没有结果,则降低精度,直至 keyword 长度为 1
+            if (collector.getTotalHits() < 1 && !SearchUtils.isKeywordInvalid(keyword) && keyword.length() > 1) {
+                return recursivelyGetPCBGoodsIds(keyword.substring(0, keyword.length() - 1));
+            }
+            return keyword;
+        } finally {
+            SearchUtils.releaseIndexSearcher(indexSearcher);
+        }
+    }
+
+    /**
+     * 设置 PCB 批次过滤条件
+     *
+     * @param filters 指定的过滤条件
+     * @param query   原查询
+     */
+    private void setPCBGoodsFilter(Map<FilterField, Object> filters, BooleanQuery query) {
+        Object status;
+        // 筛选状态
+        if (!CollectionUtils.isEmpty(filters) && !StringUtils.isEmpty(filters.get(FilterField.GOODS_STATUS))) {
+            // 如果明确指定了状态,则直接过滤批次(结果中不包括没有批次的 PCB)
+            status = filters.get(FilterField.GOODS_STATUS);
+            filter(status, SearchConstants.PCB_GOODS_GO_STATUS_FIELD, query);
+        } else {
+            // 如果未明确指定状态,则使用默认状态分情况进行过滤(结果中包括没有批次的 PCB)
+            status = Arrays.asList(TradeGoods.VALID_STATUS);
+            // 批次 id 不为空时,对状态过滤
+            Query goNullQuery = SearchUtils.getNullQuery(SearchConstants.PCB_GOODS_GO_ID_FIELD);
+            BooleanQuery q1 = new BooleanQuery();
+            q1.add(goNullQuery, Occur.MUST_NOT);
+            filter(status, SearchConstants.PCB_GOODS_GO_STATUS_FIELD, q1);
+            // 或者批次 id 为空(此时是PCB)
+            BooleanQuery q2 = new BooleanQuery();
+            q2.add(SearchUtils.getNullQuery(SearchConstants.PCB_GOODS_PCB_ID_FIELD), Occur.MUST_NOT);
+            q2.add(goNullQuery, Occur.MUST);
+
+            BooleanQuery booleanQuery = new BooleanQuery();
+            booleanQuery.add(q1, Occur.SHOULD);
+            booleanQuery.add(q2, Occur.SHOULD);
+            query.add(booleanQuery, Occur.FILTER);
+        }
+
+        if (CollectionUtils.isEmpty(filters)) {
+            return;
+        }
+        // 筛选类目
+        if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_KINDID))) {
+            filter(filters.get(FilterField.GOODS_KINDID), SearchConstants.PCB_GOODS_KI_ID_FIELD, query);
+        }
+
+        // 筛选品牌
+        if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_BRANDID))) {
+            filter(filters.get(FilterField.GOODS_BRANDID), SearchConstants.PCB_GOODS_BR_ID_FIELD, query);
+        }
+
+        // 筛选货币
+        if (!StringUtils.isEmpty(filters.get(FilterField.GOODS_CRNAME))) {
+            filter(filters.get(FilterField.GOODS_CRNAME), SearchConstants.PCB_GOODS_CRNAME_FIELD, query);
+        }
+
+        // 价格筛选
+        Object minPriceRmb = filters.get(FilterField.GOODS_MINPRICERMB);
+        Object maxPriceRmb = filters.get(FilterField.GOODS_MAXPRICERMB);
+        Object minPriceUsd = filters.get(FilterField.GOODS_MINPRICEUSD);
+        Object maxPriceUsd = filters.get(FilterField.GOODS_MAXPRICEUSD);
+        // 筛选人民币价格
+        if (!StringUtils.isEmpty(minPriceRmb) || !StringUtils.isEmpty(maxPriceRmb)) {
+            Double minPrice = null;
+            Double maxPrice = null;
+            if (!StringUtils.isEmpty(minPriceRmb)) {
+                minPrice = Double.valueOf(minPriceRmb.toString());
+            }
+            if (!StringUtils.isEmpty(maxPriceRmb)) {
+                maxPrice = Double.valueOf(maxPriceRmb.toString());
+            }
+            query.add(NumericRangeQuery.newDoubleRange(SearchConstants.PCB_GOODS_GO_MINPRICERMB_FIELD,
+                    minPrice, maxPrice, true, true), BooleanClause.Occur.FILTER);
+        }
+        // 筛选美元价格
+        if (!StringUtils.isEmpty(minPriceUsd) || !StringUtils.isEmpty(maxPriceUsd)) {
+            Double minPrice = null;
+            Double maxPrice = null;
+            if (!StringUtils.isEmpty(minPriceUsd)) {
+                minPrice = Double.valueOf(minPriceUsd.toString());
+            }
+            if (!StringUtils.isEmpty(maxPriceUsd)) {
+                maxPrice = Double.valueOf(maxPriceUsd.toString());
+            }
+            query.add(NumericRangeQuery.newDoubleRange(SearchConstants.PCB_GOODS_GO_MINPRICEUSD_FIELD,
+                    minPrice, maxPrice, true, true), BooleanClause.Occur.FILTER);
+        }
+    }
+
+    /**
+     * PCB 批次排序规则
+     */
+    private Sort sortPCBGoods(String keyword, com.uas.search.constant.model.Sort sort) {
+        List<SortField> sortFieldList = new ArrayList<>();
+        sortFieldList.add(SortField.FIELD_SCORE);
+        if (sort != null) {
+            com.uas.search.constant.model.Sort.Field field = sort.getField();
+            if (field == null) {
+                throw new IllegalArgumentException("排序字段不可为空:" + sort);
+            }
+            boolean reverse = sort.isReverse();
+            if (field == RESERVE) {
+                // 库存
+                sortFieldList.addAll(Arrays.asList(
+                        // 降序时,默认值为最小值;升序时,默认值为最大值
+                        sortField(SearchConstants.PCB_GOODS_GO_RESERVE_FIELD, Type.DOUBLE, reverse, reverse ? Double.MIN_VALUE : Double.MAX_VALUE),
+                        sortField(SearchConstants.PCB_GOODS_GO_MINPRICERMB_FIELD, Type.DOUBLE, false, Double.MAX_VALUE),
+                        sortField(SearchConstants.PCB_GOODS_GO_MINPRICEUSD_FIELD, Type.DOUBLE, false, Double.MAX_VALUE)
+                ));
+            } else if (field == PRICE) {
+                // 价格
+                sortFieldList.addAll(Arrays.asList(
+                        // 降序时,默认值为最小值;升序时,默认值为最大值
+                        sortField(SearchConstants.PCB_GOODS_GO_MINPRICERMB_FIELD, Type.DOUBLE, reverse, reverse ? Double.MIN_VALUE : Double.MAX_VALUE),
+                        sortField(SearchConstants.PCB_GOODS_GO_MINPRICEUSD_FIELD, Type.DOUBLE, reverse, reverse ? Double.MIN_VALUE : Double.MAX_VALUE),
+                        sortField(SearchConstants.PCB_GOODS_GO_RESERVE_FIELD, Type.DOUBLE, true, Double.MIN_VALUE)
+                ));
+            } else {
+                throw new IllegalArgumentException("不支持该排序方式:" + field);
+            }
+        } else {
+            // 默认(综合排序)
+            sortFieldList.addAll(Arrays.asList(
+                    sortField(SearchConstants.PCB_GOODS_GO_MINPRICERMB_FIELD, Type.DOUBLE, false, Double.MAX_VALUE),
+                    sortField(SearchConstants.PCB_GOODS_GO_MINPRICEUSD_FIELD, Type.DOUBLE, false, Double.MAX_VALUE),
+                    sortField(SearchConstants.PCB_GOODS_GO_RESERVE_FIELD, Type.DOUBLE, true, Double.MIN_VALUE),
+                    sortField(SearchConstants.PCB_GOODS_GO_MINDELIVERY_FIELD, Type.LONG, false, Long.MAX_VALUE)
+            ));
+        }
+        sortFieldList.addAll(Arrays.asList(
+                // 如果仍然无法得到正确结果,就根据按照型号等顺序严格排列
+                new SortField(SearchConstants.PCB_GOODS_PR_PCMPCODE_FIELD, new StringFieldComparatorSource(keyword, false)),
+                new SortField(SearchConstants.PCB_GOODS_BR_NAME_EN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, false)),
+                new SortField(SearchConstants.PCB_GOODS_BR_NAME_CN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, false)),
+                new SortField(SearchConstants.PCB_GOODS_KI_NAME_CN_UNTOKENIZED_FIELD, new StringFieldComparatorSource(keyword, false)),
+                sortField(SearchConstants.PCB_GOODS_GO_UPDATE_DATE_FIELD, Type.LONG, true, Long.MIN_VALUE)
+        ));
+        SortField[] sortFields = new SortField[sortFieldList.size()];
+        sortFieldList.toArray(sortFields);
+        return new Sort(sortFields);
+    }
+
 	@Override
 	public List<Map<String, Object>> collectBySearchGoods(String keyword, CollectField collectedField,
 			Map<FilterField, Object> filters) throws IOException {
@@ -1086,7 +1310,6 @@ public class SearchServiceImpl implements SearchService {
 			result = collectBySearchGoods(keyword, null, true, collectedField, filters);
 		}
 		return result;
-
 	}
 
 	/**
@@ -1202,6 +1425,82 @@ public class SearchServiceImpl implements SearchService {
 		return booleanQuery;
 	}
 
+    @Override
+    public List<Map<String, Object>> collectBySearchPCBGoods(String keyword, CollectField collectedField,
+                                                          Map<FilterField, Object> filters) throws IOException {
+        keyword = recursivelyGetPCBGoodsIds(keyword);
+        if (collectedField == null) {
+            throw new IllegalArgumentException("参数为空:collectedField");
+        }
+        IndexSearcher indexSearcher = SearchUtils.getIndexSearcher(SearchConstants.PCB_GOODS_TABLE_NAME);
+
+        List<Map<String, Object>> result = new ArrayList<>();
+        try {
+            BooleanQuery booleanQuery = queryPCBGoods(keyword);
+            setPCBGoodsFilter(filters, booleanQuery);
+            logger.info(booleanQuery.toString());
+
+            // 统计
+            String uniqueField;
+            Set<String> fieldsToLoad = new HashSet<>();
+            switch (collectedField) {
+                case GOODS_KIND:
+                    uniqueField = SearchConstants.PCB_GOODS_KI_ID_FIELD;
+                    fieldsToLoad.add(SearchConstants.PCB_GOODS_KI_ID_FIELD);
+                    fieldsToLoad.add(SearchConstants.PCB_GOODS_KI_NAME_CN_FIELD);
+                    break;
+                case GOODS_BRAND:
+                    uniqueField = SearchConstants.PCB_GOODS_BR_ID_FIELD;
+                    fieldsToLoad.add(SearchConstants.PCB_GOODS_BR_ID_FIELD);
+                    fieldsToLoad.add(SearchConstants.PCB_GOODS_BR_UUID_FIELD);
+                    fieldsToLoad.add(SearchConstants.PCB_GOODS_BR_NAME_CN_FIELD);
+                    fieldsToLoad.add(SearchConstants.PCB_GOODS_BR_NAME_EN_FIELD);
+                    break;
+                case GOODS_CRNAME:
+                    uniqueField = SearchConstants.PCB_GOODS_CRNAME_FIELD;
+                    fieldsToLoad.add(SearchConstants.PCB_GOODS_CRNAME_FIELD);
+                    break;
+                default:
+                    throw new IllegalArgumentException("不支持该统计字段:" + collectedField);
+            }
+            GoodsGroupCollector collector = new GoodsGroupCollector(uniqueField, fieldsToLoad);
+            indexSearcher.search(booleanQuery, collector);
+            result = collector.getValues();
+        } catch (IOException e) {
+            logger.error("", e);
+        } finally {
+            SearchUtils.releaseIndexSearcher(indexSearcher);
+        }
+        return result;
+    }
+
+
+    /**
+     * 获取查询 PCB 批次的query
+     *
+     * @param keyword
+     * @return
+     */
+    private BooleanQuery queryPCBGoods(String keyword) {
+        BooleanQuery booleanQuery = new BooleanQuery();
+        if (!SearchUtils.isKeywordInvalid(keyword)) {
+            booleanQuery.add(setPCBGoodsBoost(keyword), BooleanClause.Occur.MUST);
+        }
+        return booleanQuery;
+    }
+
+    /**
+     * 搜索 PCB,设置boost
+     */
+    private BooleanQuery setPCBGoodsBoost(String keyword) {
+        BooleanQuery booleanQuery = new BooleanQuery();
+        // 前缀搜索(字段并未分词,进行分词搜索时,会有边界问题,如搜索 'BC807-40,215')
+        booleanQuery.add(new PrefixQuery(new Term(SearchConstants.PCB_GOODS_PR_PCMPCODE_FIELD, keyword.toLowerCase())), BooleanClause.Occur.SHOULD);
+        // PCB
+        booleanQuery.add(createQuery(SearchConstants.PCB_GOODS_PR_PCMPCODE_FIELD, keyword, 100), Occur.SHOULD);
+        return booleanQuery;
+    }
+
     private BooleanQuery createQuery(String field, String keyword, float boost){
        return createQuery(field, keyword, false, boost);
     }
@@ -1274,6 +1573,12 @@ public class SearchServiceImpl implements SearchService {
 				SearchUtils.getDocumentById(SearchConstants.GOODS_TABLE_NAME, SearchConstants.GOODS_GO_ID_FIELD, id));
 	}
 
+    @Override
+    public PCBGoods getPCBGoods(String id) throws IOException {
+        return DocumentToObjectUtils.toPCBGoods(
+                SearchUtils.getDocumentById(SearchConstants.PCB_GOODS_TABLE_NAME, SearchConstants.PCB_GOODS_GO_ID_FIELD, id));
+    }
+
 	@Override
 	public SPage<Object> getObjects(String tableName, String keyword, String field, Boolean tokenized, @NotEmpty("page") Integer page, @NotEmpty("size") Integer size) throws IOException {
 		if (keyword == null) {

+ 95 - 1
mall-search/src/main/java/com/uas/search/util/DocumentToObjectUtils.java

@@ -31,7 +31,9 @@ public class DocumentToObjectUtils {
 			return toComponent(document);
 		} else if (tableName.equals(SearchConstants.GOODS_TABLE_NAME)) {
 			return toGoods(document);
-		} else if (tableName.equals(SearchConstants.ORDER_TABLE_NAME)) {
+		} else if (tableName.equals(SearchConstants.PCB_GOODS_TABLE_NAME)) {
+            return toPCBGoods(document);
+        } else if (tableName.equals(SearchConstants.ORDER_TABLE_NAME)) {
 			return toOrder(document);
 		} else if (tableName.equals(SearchConstants.ORDER_INVOICE_TABLE_NAME)) {
 			return toOrderInvoice(document);
@@ -297,6 +299,98 @@ public class DocumentToObjectUtils {
         return goods;
     }
 
+    /**
+     * 将 Document 转换为 PCB 批次对象
+     *
+     * @param document
+     * @return
+     */
+    public static PCBGoods toPCBGoods(Document document) {
+        if (document == null) {
+            return null;
+        }
+        PCBGoods pcbGoods = new PCBGoods();
+        String goId = document.get(SearchConstants.PCB_GOODS_GO_ID_FIELD);
+        if (!StringUtils.isEmpty(goId) && !goId.equals(ObjectToDocumentUtils.NULL_VALUE)) {
+            TradeGoods tradeGoods = new TradeGoods();
+            tradeGoods.setId(Long.valueOf(goId));
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_GO_RESERVE_FIELD))) {
+                tradeGoods.setReserve(Double.valueOf(document.get(SearchConstants.PCB_GOODS_GO_RESERVE_FIELD)));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_GO_STATUS_FIELD))) {
+                tradeGoods.setStatus(Long.valueOf(document.get(SearchConstants.PCB_GOODS_GO_STATUS_FIELD)));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_GO_MINPRICERMB_FIELD))) {
+                tradeGoods.setMinPriceRMB(Double.valueOf(document.get(SearchConstants.PCB_GOODS_GO_MINPRICERMB_FIELD)));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_GO_MINPRICEUSD_FIELD))) {
+                tradeGoods.setMinPriceUSD(Double.valueOf(document.get(SearchConstants.PCB_GOODS_GO_MINPRICEUSD_FIELD)));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_CRNAME_FIELD))) {
+                tradeGoods.setCrName(document.get(SearchConstants.PCB_GOODS_CRNAME_FIELD));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_GO_VISIT_COUNT_FIELD))) {
+                tradeGoods.setVisitCount(Long.valueOf(document.get(SearchConstants.PCB_GOODS_GO_VISIT_COUNT_FIELD)));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_GO_UPDATE_DATE_FIELD))) {
+                tradeGoods.setUpdateDate(new Date(Long.valueOf(document.get(SearchConstants.PCB_GOODS_GO_UPDATE_DATE_FIELD))));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_GO_MINDELIVERY_FIELD))) {
+                tradeGoods.setMinDelivery(Long.valueOf(document.get(SearchConstants.PCB_GOODS_GO_MINDELIVERY_FIELD)));
+            }
+            pcbGoods.setTradeGoods(tradeGoods);
+        }
+
+        String cmpId = document.get(SearchConstants.PCB_GOODS_PCB_ID_FIELD);
+        if (!StringUtils.isEmpty(cmpId) && !cmpId.equals(ObjectToDocumentUtils.NULL_VALUE)) {
+            PCB pcb = new PCB();
+            pcb.setId(Long.valueOf(cmpId));
+
+            Kind kind = new Kind();
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_KI_ID_FIELD))) {
+                kind.setId(Long.valueOf(document.get(SearchConstants.PCB_GOODS_KI_ID_FIELD)));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_KI_NAME_CN_FIELD))) {
+                kind.setNameCn(document.get(SearchConstants.PCB_GOODS_KI_NAME_CN_FIELD));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_KI_LEVEL_FIELD))) {
+                kind.setLevel(Short.valueOf(document.get(SearchConstants.PCB_GOODS_KI_LEVEL_FIELD)));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_KI_ISLEAF_FIELD))) {
+                kind.setIsLeaf(Short.valueOf(document.get(SearchConstants.PCB_GOODS_KI_ISLEAF_FIELD)));
+            }
+            pcb.setKind(kind);
+
+            Brand brand = new Brand();
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_BR_ID_FIELD))) {
+                brand.setId(Long.valueOf(document.get(SearchConstants.PCB_GOODS_BR_ID_FIELD)));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_BR_NAME_CN_FIELD))) {
+                brand.setNameCn(document.get(SearchConstants.PCB_GOODS_BR_NAME_CN_FIELD));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_BR_UUID_FIELD))) {
+                brand.setUuid(document.get(SearchConstants.PCB_GOODS_BR_UUID_FIELD));
+            }
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_BR_NAME_EN_FIELD))) {
+                brand.setNameEn(document.get(SearchConstants.PCB_GOODS_BR_NAME_EN_FIELD));
+            }
+            pcb.setBrand(brand);
+
+            Products products = new Products();
+            if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_PR_ID_FIELD))) {
+                products.setId(Long.valueOf(document.get(SearchConstants.PCB_GOODS_PR_ID_FIELD)));
+                if (!StringUtils.isEmpty(document.get(SearchConstants.PCB_GOODS_PR_PCMPCODE_FIELD))) {
+                    products.setPcmpCode(document.get(SearchConstants.PCB_GOODS_PR_PCMPCODE_FIELD));
+                }
+            }
+            pcb.setProducts(products);
+
+            pcbGoods.setPcb(pcb);
+        }
+
+        return pcbGoods;
+    }
+
 	/**
 	 * 将Document转换为销售单对象
 	 * 

+ 127 - 12
mall-search/src/main/java/com/uas/search/util/ObjectToDocumentUtils.java

@@ -12,7 +12,7 @@ import java.util.Set;
 
 /**
  * 将对象转换为Document的工具类
- * 
+ *
  * @author sunyj
  * @since 2016年10月17日 上午11:32:19
  */
@@ -25,12 +25,12 @@ public class ObjectToDocumentUtils {
 
 	/**
 	 * 将对象转为Document
-	 * 
+	 *
 	 * @param object
 	 *            对象,可为Kind、Brand、Component、
 	 *            Order、OrderInvoice、Purchase、
 	 *            PurchaseInvoice
-	 * 
+	 *
 	 * @return 转换的Document
 	 */
 	public static Document toDocument(Object object) {
@@ -45,6 +45,8 @@ public class ObjectToDocumentUtils {
 			return toDocument((Component) object);
 		} else if (object instanceof Goods) {
 			return toDocument((Goods) object);
+		} else if (object instanceof PCBGoods) {
+            return toDocument((PCBGoods) object);
 		} else if (object instanceof Order) {
 			return toDocument((Order) object);
 		} else if (object instanceof OrderInvoice) {
@@ -53,14 +55,14 @@ public class ObjectToDocumentUtils {
 			return toDocument((Purchase) object);
 		} else if (object instanceof PurchaseInvoice) {
 			return toDocument((PurchaseInvoice) object);
-		} else {
+        } else {
 			throw new SearchException("不支持将以下类型转换为Document:" + object.getClass().getName());
 		}
 	}
 
 	/**
 	 * Kind对象转为Document
-	 * 
+	 *
 	 * @param kind
 	 * @return
 	 */
@@ -91,7 +93,7 @@ public class ObjectToDocumentUtils {
 
 	/**
 	 * Brand对象转为Document
-	 * 
+	 *
 	 * @param brand
 	 * @return
 	 */
@@ -128,7 +130,7 @@ public class ObjectToDocumentUtils {
 
 	/**
 	 * Component对象转为Document
-	 * 
+	 *
 	 * @param component
 	 * @return
 	 */
@@ -232,7 +234,7 @@ public class ObjectToDocumentUtils {
 
 	/**
 	 * Goods对象转为Document
-	 * 
+	 *
 	 * @param goods
 	 * @return
 	 */
@@ -400,9 +402,122 @@ public class ObjectToDocumentUtils {
         return document;
 	}
 
+    /**
+     * PCBGoods 对象转为 Document
+     *
+     * @param pcbGoods
+     * @return
+     */
+    public static Document toDocument(PCBGoods pcbGoods) {
+        if (pcbGoods == null ||
+                (pcbGoods.getTradeGoods() != null && pcbGoods.getTradeGoods().getId() == null) ||
+                pcbGoods.getPcb() == null || pcbGoods.getPcb().getId() == null ||
+                pcbGoods.getPcb().getKind() == null ||
+                pcbGoods.getPcb().getBrand() == null ||
+                pcbGoods.getPcb().getProducts() == null || pcbGoods.getPcb().getProducts().getId() == null) {
+            return null;
+        }
+
+        String primaryKey = "";
+        Document document = new Document();
+        if(pcbGoods.getTradeGoods() != null){
+            TradeGoods tradeGoods = pcbGoods.getTradeGoods();
+            document.add(new StringField(SearchConstants.PCB_GOODS_GO_ID_FIELD, String.valueOf(tradeGoods.getId()), Store.YES));
+            if (tradeGoods.getReserve() != null) {
+                document.add(new DoubleDocValuesField(SearchConstants.PCB_GOODS_GO_RESERVE_FIELD, tradeGoods.getReserve()));
+                document.add(new DoubleField(SearchConstants.PCB_GOODS_GO_RESERVE_FIELD, tradeGoods.getReserve(), Store.YES));
+            }
+            if (tradeGoods.getStatus() != null) {
+                document.add(new StringField(SearchConstants.PCB_GOODS_GO_STATUS_FIELD, String.valueOf(tradeGoods.getStatus()), Store.YES));
+            }
+            if (tradeGoods.getMinPriceRMB() != null) {
+                document.add(
+                        new DoubleDocValuesField(SearchConstants.PCB_GOODS_GO_MINPRICERMB_FIELD, tradeGoods.getMinPriceRMB()));
+                document.add(
+                        new DoubleField(SearchConstants.PCB_GOODS_GO_MINPRICERMB_FIELD, tradeGoods.getMinPriceRMB(), Store.YES));
+            }
+            if (tradeGoods.getMinPriceUSD() != null) {
+                document.add(
+                        new DoubleDocValuesField(SearchConstants.PCB_GOODS_GO_MINPRICEUSD_FIELD, tradeGoods.getMinPriceUSD()));
+                document.add(
+                        new DoubleField(SearchConstants.PCB_GOODS_GO_MINPRICEUSD_FIELD, tradeGoods.getMinPriceUSD(), Store.YES));
+            }
+            if (tradeGoods.getCrName() != null) {
+                document.add(new TextField(SearchConstants.PCB_GOODS_CRNAME_FIELD, tradeGoods.getCrName(), Store.YES));
+            }
+            if (tradeGoods.getVisitCount() != null) {
+                document.add(new DoubleDocValuesField(SearchConstants.PCB_GOODS_GO_VISIT_COUNT_FIELD, tradeGoods.getVisitCount()));
+                document.add(new LongField(SearchConstants.PCB_GOODS_GO_VISIT_COUNT_FIELD, tradeGoods.getVisitCount(), Store.YES));
+            }
+            if (tradeGoods.getUpdateDate() != null) {
+                document.add(new DoubleDocValuesField(SearchConstants.PCB_GOODS_GO_UPDATE_DATE_FIELD, tradeGoods.getUpdateDate().getTime()));
+                document.add(new LongField(SearchConstants.PCB_GOODS_GO_UPDATE_DATE_FIELD, tradeGoods.getUpdateDate().getTime(), Store.YES));
+            }
+            if (tradeGoods.getMinDelivery() != null) {
+                document.add(new DoubleDocValuesField(SearchConstants.PCB_GOODS_GO_MINDELIVERY_FIELD, tradeGoods.getMinDelivery()));
+                document.add(new LongField(SearchConstants.PCB_GOODS_GO_MINDELIVERY_FIELD, tradeGoods.getMinDelivery(), Store.YES));
+            }
+            primaryKey += tradeGoods.getId();
+        } else {
+            // 批次 id 为 null 时,存默认值,以便于后期搜索时做空值过滤
+            document.add(new StringField(SearchConstants.PCB_GOODS_GO_ID_FIELD, NULL_VALUE, Store.YES));
+            primaryKey += NULL_VALUE;
+        }
+
+        PCB pcb = pcbGoods.getPcb();
+        document.add(new StringField(SearchConstants.PCB_GOODS_PCB_ID_FIELD, String.valueOf(pcb.getId()), Store.YES));
+
+        Kind kind = pcb.getKind();
+        if (kind.getId() != null) {
+            document.add(new StringField(SearchConstants.PCB_GOODS_KI_ID_FIELD, String.valueOf(kind.getId()), Store.YES));
+        }
+        if (kind.getNameCn() != null) {
+            document.add(new TextField(SearchConstants.PCB_GOODS_KI_NAME_CN_FIELD, kind.getNameCn(), Store.YES));
+            document.add(new StringField(SearchConstants.PCB_GOODS_KI_NAME_CN_UNTOKENIZED_FIELD, kind.getNameCn().toLowerCase(), Store.YES));
+            document.add(new BinaryDocValuesField(SearchConstants.PCB_GOODS_KI_NAME_CN_UNTOKENIZED_FIELD, new BytesRef(kind.getNameCn())));
+        }
+        if (kind.getLevel() != null) {
+            document.add(new NumericDocValuesField(SearchConstants.PCB_GOODS_KI_LEVEL_FIELD, kind.getLevel()));
+            document.add(new LongField(SearchConstants.PCB_GOODS_KI_LEVEL_FIELD, kind.getLevel(), Store.YES));
+        }
+        if (kind.getIsLeaf() != null) {
+            document.add(
+                    new StringField(SearchConstants.PCB_GOODS_KI_ISLEAF_FIELD, String.valueOf(kind.getIsLeaf()), Store.YES));
+        }
+
+        Brand brand = pcb.getBrand();
+        if (brand.getId() != null) {
+            document.add(new StringField(SearchConstants.PCB_GOODS_BR_ID_FIELD, String.valueOf(brand.getId()), Store.YES));
+        }
+        if (brand.getNameCn() != null) {
+            document.add(new TextField(SearchConstants.PCB_GOODS_BR_NAME_CN_FIELD, brand.getNameCn(), Store.YES));
+            document.add(new StringField(SearchConstants.PCB_GOODS_BR_NAME_CN_UNTOKENIZED_FIELD, brand.getNameCn().toLowerCase(), Store.YES));
+            document.add(new BinaryDocValuesField(SearchConstants.PCB_GOODS_BR_NAME_CN_UNTOKENIZED_FIELD, new BytesRef(brand.getNameCn())));
+        }
+        if (brand.getNameEn() != null) {
+            document.add(new TextField(SearchConstants.PCB_GOODS_BR_NAME_EN_FIELD, brand.getNameEn(), Store.YES));
+            document.add(new StringField(SearchConstants.PCB_GOODS_BR_NAME_EN_UNTOKENIZED_FIELD, brand.getNameEn().toLowerCase(), Store.YES));
+            document.add(new BinaryDocValuesField(SearchConstants.PCB_GOODS_BR_NAME_EN_UNTOKENIZED_FIELD, new BytesRef(brand.getNameEn())));
+        }
+        if (brand.getUuid() != null) {
+            document.add(new StringField(SearchConstants.PCB_GOODS_BR_UUID_FIELD, brand.getUuid(), Store.YES));
+        }
+        primaryKey += "_" + pcb.getId();
+
+        Products products = pcb.getProducts();
+        document.add(new StringField(SearchConstants.PCB_GOODS_PR_ID_FIELD, String.valueOf(products.getId()), Store.YES));
+        if (products.getPcmpCode() != null) {
+            document.add(new StringField(SearchConstants.PCB_GOODS_PR_PCMPCODE_FIELD, products.getPcmpCode().toLowerCase(), Store.YES));
+            document.add(new BinaryDocValuesField(SearchConstants.PCB_GOODS_PR_PCMPCODE_FIELD, new BytesRef(products.getPcmpCode())));
+        }
+
+        document.add(new StringField(SearchConstants.PCB_GOODS_PRIMARY_KEY_FIELD, primaryKey, Store.YES));
+        return document;
+    }
+
 	/**
 	 * Order对象转为Document
-	 * 
+	 *
 	 * @param order
 	 *            Order对象
 	 * @return 转换的Document
@@ -449,7 +564,7 @@ public class ObjectToDocumentUtils {
 
 	/**
 	 * OrderInvoice对象转为Document
-	 * 
+	 *
 	 * @param orderInvoice
 	 *            OrderInvoice对象
 	 * @return 转换的Document
@@ -487,7 +602,7 @@ public class ObjectToDocumentUtils {
 
 	/**
 	 * Purchase对象转为Document
-	 * 
+	 *
 	 * @param purchase
 	 *            Purchase对象
 	 * @return 转换的Document
@@ -517,7 +632,7 @@ public class ObjectToDocumentUtils {
 
 	/**
 	 * PurchaseInvoice对象转为Document
-	 * 
+	 *
 	 * @param purchaseInvoice
 	 *            PurchaseInvoice对象
 	 * @return 转换的Document

+ 4 - 1
mall-search/src/main/java/com/uas/search/util/SearchUtils.java

@@ -392,6 +392,7 @@ public class SearchUtils {
 		tableNames.add(SearchConstants.BRAND_TABLE_NAME);
 		tableNames.add(SearchConstants.COMPONENT_TABLE_NAME);
 		tableNames.add(SearchConstants.GOODS_TABLE_NAME);
+        tableNames.add(SearchConstants.PCB_GOODS_TABLE_NAME);
 		tableNames.add(SearchConstants.ORDER_TABLE_NAME);
 		tableNames.add(SearchConstants.ORDER_INVOICE_TABLE_NAME);
 		tableNames.add(SearchConstants.PURCHASE_TABLE_NAME);
@@ -417,7 +418,9 @@ public class SearchUtils {
 			return SearchConstants.COMPONENT_TABLE_NAME;
 		} else if (clazz == Goods.class) {
 			return SearchConstants.GOODS_TABLE_NAME;
-		} else if (clazz == Order.class) {
+		} else if (clazz == PCBGoods.class) {
+            return SearchConstants.PCB_GOODS_TABLE_NAME;
+        } else if (clazz == Order.class) {
 			return SearchConstants.ORDER_TABLE_NAME;
 		} else if (clazz == OrderInvoice.class) {
 			return SearchConstants.ORDER_INVOICE_TABLE_NAME;

+ 7 - 1
mall-search/src/main/webapp/WEB-INF/views/console.html

@@ -21,6 +21,8 @@
 					<li><a target="_blank">search/components?keyword=aaa</a></li>
 					<li>search/goodsIds?keyword=aaa&amp;params={"filters":{"GOODS_MINPRICEUSD":"0","GOODS_KINDID":["401","23"],"GOODS_MAXPRICERMB":"10","GOODS_BRANDID":["45"],"GOODS_MAXPRICEUSD":"10","GOODS_STORE_TYPE":["DISTRIBUTION"],"GOODS_MINPRICERMB":"0","GOODS_CRNAME":["RMB-USD"]},"page":1,"size":8,"sort":{"field":"RESERVE","reverse":true}}</li>
 					<li><a target="_blank">search/goodsIds?keyword=aaa</a></li>
+                    <li>search/pcbGoodsIds?keyword=aaa&amp;params={"filters":{"GOODS_MINPRICEUSD":"0","GOODS_KINDID":["401","23"],"GOODS_MAXPRICERMB":"10","GOODS_BRANDID":["45"],"GOODS_MAXPRICEUSD":"10","GOODS_MINPRICERMB":"0","GOODS_CRNAME":["RMB-USD"]},"page":1,"size":8,"sort":{"field":"RESERVE","reverse":true}}</li>
+                    <li><a target="_blank">search/pcbGoodsIds?keyword=aaa</a></li>
 				</ol>
 
 
@@ -31,6 +33,7 @@
 					<li><a target="_blank">search/kindIdsByComponent?keyword=aaa&brandId=56</a></li>
 					<li><a target="_blank">search/brandIdsByComponent?keyword=aaa&kindId=304</a></li>
 					<li><a target="_blank">search/collectBySearchGoods?keyword=23&collectedField=GOODS_KIND&filters={"GOODS_KINDID":[401,23],"GOODS_BRANDID":45,"GOODS_STORE_TYPE":"DISTRIBUTION","GOODS_CRNAME":"RMB-USD"}</a></li>
+                    <li><a target="_blank">search/collectBySearchPCBGoods?keyword=23&collectedField=GOODS_KIND&filters={"GOODS_KINDID":[401,23],"GOODS_BRANDID":45,"GOODS_CRNAME":"RMB-USD"}</a></li>
 				</ol>
 
 				<strong><li class="title">联想词</li></strong>
@@ -51,6 +54,7 @@
 					<li><a target="_blank">search/brand/12</a></li>
 					<li><a target="_blank">search/component/12</a></li>
 					<li><a target="_blank">search/goods/123222</a></li>
+                    <li><a target="_blank">search/pcbGoods/123222</a></li>
 					<li><a target="_blank">search/objects?tableName=product$brand&keyword=松下&field=br_name_cn&tokenized=true&page=1&size=10</a></li>
 					<li><a target="_blank">search/objects?tableName=product$brand&page=1&size=10</a></li>
 					<li><a target="_blank">search/allObjectsToFiles?tableName=product$brand</a></li>
@@ -80,12 +84,14 @@
 
 			<h2>3. 索引修改</h2>
 			<ol>
-				<li>index/create?tableNames=product$kind,product$brand,product$component,trade$invoice_fmor,trade$order,trade$purchase,trade$invoice_fmpu,v$product$cmpgoods&componentFromFiles=false</li>
+				<li>index/create?tableNames=product$kind,product$brand,product$component,trade$invoice_fmor,trade$order,trade$purchase,trade$invoice_fmpu,v$product$cmpgoods,pcb_goods&componentFromFiles=false</li>
 				<li><a target="_blank">index/create</a></li>
 				<li><a target="_blank">index/create?tableNames=product$brand,trade$order</a></li>
 				<li><a target="_blank">index/multiDownloadComponent?threads=2&startFileIndex=0&endFileIndex=10000000&validateResult=current</a></li>
 				<li>index/multiDownloadGoods?threads=2&startFileIndex=0&endFileIndex=1&validateResult=all</li>
 				<li><a target="_blank">index/multiDownloadGoods?threads=2&startFileIndex=0&endFileIndex=1</a></li>
+                <li><a target="_blank">index/multiDownloadPCB?threads=2&startFileIndex=0&endFileIndex=1</a></li>
+                <li><a target="_blank">index/multiDownloadPCBGoods?threads=2&startFileIndex=0&endFileIndex=1</a></li>
 				<li><a target="_blank">index/listen/start?interval=10</a></li>
 				<li><a target="_blank">index/listen/stop</a></li>
 				<li><a target="_blank">index/listen/restart</a></li>