032 搭建搜索微服務01----向ElasticSearch中導入數據--通過Feign實現微服務之間的相互調用


1.創建搜索服務

創建module:

 

Pom文件:

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <parent>
        <artifactId>leyou</artifactId>
        <groupId>lucky.leyou.parent</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>lucky.leyou.search</groupId>
    <artifactId>leyou-search</artifactId>

    <dependencies>
        <!-- web -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!-- elasticsearch -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-elasticsearch</artifactId>
        </dependency>
        <!-- 因為要leyou-search模塊也是一個微服務,必須引入eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- leyou-search模塊需要調用其他微服務,需要使用feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>lucky.leyou.item</groupId>
            <artifactId>leyou-item-interface</artifactId>
        </dependency>

        <dependency>
            <groupId>lucky.leyou.common</groupId>
            <artifactId>leyou-common</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>


</project>

application.yml:

server:
  port: 8083
spring:
  application:
    name: search-service
  data:
    elasticsearch:
      cluster-name: leyou
      cluster-nodes: 127.0.0.1:9300  # 程序連接es的端口號是9300
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 10 #設置拉取服務的時間
  instance:
    lease-renewal-interval-in-seconds: 5 # 每隔5秒發送一次心跳
    lease-expiration-duration-in-seconds: 10 # 10秒不發送就過期

引導類:

package lucky.leyou;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.openfeign.EnableFeignClients;

@SpringBootApplication  
@EnableDiscoveryClient //能夠讓注冊中心能夠發現,掃描到該微服務
@EnableFeignClients // 開啟feign客戶端
public class LeyouSearchService {

    public static void main(String[] args) {
        SpringApplication.run(LeyouSearchService.class, args);
    }
}

2.索引庫數據格式分析

接下來,我們需要商品數據導入索引庫,便於用戶搜索。

那么問題來了,我們有SPU和SKU,到底如何保存到索引庫?

<1>以結果為導向

大家來看下搜索結果頁:

 

可以看到,每一個搜索結果都有至少1個商品,當我們選擇大圖下方的小圖,商品會跟着變化。

因此,搜索的結果是SPU,即多個SKU的集合

既然搜索的結果是SPU,那么我們索引庫中存儲的應該也是SPU,但是卻需要包含SKU的信息。

 <2>需要什么數據

再來看看頁面中有什么數據:

 

直觀能看到的:圖片、價格、標題、副標題

暗藏的數據:spu的id,sku的id

另外,頁面還有過濾條件:

 

這些過濾條件也都需要存儲到索引庫中,包括:

商品分類、品牌、可用來搜索的規格參數等

綜上所述,我們需要的數據格式有:

spuId、SkuId、商品分類id、品牌id、圖片、價格、商品的創建時間、sku信息集、可搜索的規格參數

<3>最終的數據結構

我們創建一個實體類,封裝要保存到索引庫的數據,並設置映射屬性:

package lucky.leyou.domain;

import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.Document;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;

import java.util.Date;
import java.util.List;
import java.util.Map;

/**
 * elasticsearch索引對應的實體類
 * @Document 作用在類,標記實體類為文檔對象,一般有四個屬性:indexName:對應索引庫名稱 type:對應在索引庫中的類型,
 * shards:分片數量,默認5,replicas:副本數量,默認1
 * @Id 作用在成員變量,標記一個字段作為id主鍵
 * @Field 作用在成員變量,標記為文檔的字段,並指定字段映射屬性
 * 注意:String類型的數據要加注解,因為String有兩種類型:keywords、text
 */
@Document(indexName = "goods", type = "docs", shards = 1, replicas = 0)
public class Goods {
    @Id
    private Long id; // spuId
    @Field(type = FieldType.Text, analyzer = "ik_max_word")
    private String all; // 所有需要被搜索的信息,包含標題,分類,甚至品牌
    @Field(type = FieldType.Keyword, index = false)
    private String subTitle;// 賣點,將subTitle注解為keyword,不需要索引即分詞
    private Long brandId;// 品牌id
    private Long cid1;// 1級分類id
    private Long cid2;// 2級分類id
    private Long cid3;// 3級分類id
    private Date createTime;// 創建時間
    private List<Long> price;// 價格
    @Field(type = FieldType.Keyword, index = false)
    private String skus;// List<sku>信息的json結構
    private Map<String, Object> specs;// 可搜索的規格參數,key是參數名,值是參數值

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getAll() {
        return all;
    }

    public void setAll(String all) {
        this.all = all;
    }

    public String getSubTitle() {
        return subTitle;
    }

    public void setSubTitle(String subTitle) {
        this.subTitle = subTitle;
    }

    public Long getBrandId() {
        return brandId;
    }

    public void setBrandId(Long brandId) {
        this.brandId = brandId;
    }

    public Long getCid1() {
        return cid1;
    }

    public void setCid1(Long cid1) {
        this.cid1 = cid1;
    }

    public Long getCid2() {
        return cid2;
    }

    public void setCid2(Long cid2) {
        this.cid2 = cid2;
    }

    public Long getCid3() {
        return cid3;
    }

    public void setCid3(Long cid3) {
        this.cid3 = cid3;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }

    public List<Long> getPrice() {
        return price;
    }

    public void setPrice(List<Long> price) {
        this.price = price;
    }

    public String getSkus() {
        return skus;
    }

    public void setSkus(String skus) {
        this.skus = skus;
    }

    public Map<String, Object> getSpecs() {
        return specs;
    }

    public void setSpecs(Map<String, Object> specs) {
        this.specs = specs;
    }
}

一些特殊字段解釋:

  • all:用來進行全文檢索的字段,里面包含標題、商品分類信息

  • price:價格數組,是所有sku的價格集合。方便根據價格進行篩選過濾

  • skus:用於頁面展示的sku信息,不索引,不搜索。包含skuId、image、price、title字段

  • specs:所有規格參數的集合。key是參數名,值是參數值。

    例如:我們在specs中存儲 內存:4G,6G,顏色為紅色,轉為json就是:

{
    "specs":{
        "內存":[4G,6G],
        "顏色":"紅色"
    }
}

當存儲到索引庫時,elasticsearch會處理為兩個字段:

  • specs.內存:[4G,6G]

  • specs.顏色:紅色

另外, 對於字符串類型,還會額外存儲一個字段,這個字段不會分詞,用作聚合。

  • specs.顏色.keyword:紅色

3.商品微服務提供接口

 

索引庫中的數據來自於數據庫,我們不能直接去查詢商品的數據庫,因為真實開發中,每個微服務都是相互獨立的,包括數據庫也是一樣。所以我們只能調用商品微服務提供的接口服務。

 

先思考我們需要的數據:

 

  • SPU信息

  • SKU信息

  • SPU的詳情

  • 商品分類名稱(拼接all字段)

  • 品牌名稱

  • 規格參數

 

再思考我們需要哪些服務:

 

  • 第一:分批查詢spu的服務,已經寫過。

  • 第二:根據spuId查詢sku的服務,已經寫過

  • 第三:根據spuId查詢SpuDetail的服務,已經寫過

  • 第四:根據商品分類id,查詢商品分類名稱,沒寫過

  • 第五:根據商品品牌id,查詢商品的品牌,沒寫過

  • 第六:規格參數接口

 

因此我們需要額外提供一個查詢商品分類名稱的接口。

(1)根據分類id集合查詢商品分類名稱

在CategoryController中添加接口:

/**
     * 根據分類id查詢分類名稱
     * @param ids 分類id
     * @return
     */
    @GetMapping("names")
    public ResponseEntity<List<String>> queryNamesByIds(@RequestParam("ids")List<Long> ids){

        List<String> names = this.iCategoryService.queryNamesByIds(ids);
        if (CollectionUtils.isEmpty(names)) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(names);
    }

啟動微服務進行測試,訪問http://localhost:8081/category/names?ids=1,2,3

 

(2)通過id查詢品牌信息

BrandController.java中添加接口:

/**
     * 通過id查詢品牌信息
     * @param id 
     * @return
     */
    @GetMapping("{id}")
    public ResponseEntity<Brand> queryBrandById(@PathVariable("id") Long id){
        Brand brand=this.brandService.queryBrandById(id);
        if (brand==null) {
            return ResponseEntity.notFound().build();
        }
        return ResponseEntity.ok(brand);
    }

BrandServiceImpl.java中添加如下方法:

/**
     * 通過id查詢品牌信息
     * @param id 主鍵id
     * @return
     */
    @Override
    public Brand queryBrandById(Long id) {
        //selectByPrimaryKey通過主鍵:id查詢品牌信息
        return this.brandMapper.selectByPrimaryKey(id);
    }

重啟商品微服務測試:

瀏覽器中輸入:http://localhost:8081/brand/1528

在Navicat中查看真實數據表信息

(3)編寫FeignClient

現在,我們要在搜索微服務調用商品微服務的接口。

<1>feign使用環境搭建

第1步要在leyou-search工程中的pom文件中,引入feign的啟動器、分頁工具模塊和商品微服務依賴:leyou-item-interface

     <!-- leyou-search模塊需要調用其他微服務,需要使用feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>

        <dependency>
            <groupId>lucky.leyou.item</groupId>
            <artifactId>leyou-item-interface</artifactId>
        </dependency>

        <dependency>
            <groupId>lucky.leyou.common</groupId>
            <artifactId>leyou-common</artifactId>
        </dependency>

第2步要在leyou-search工程中的引導類上添加注解:@EnableFeignClients ,開啟feign客戶端

<2>Feign在代碼中使用

問題引出:

@FeignClient(value = "item-service")
public interface GoodsClient {

    /**
     * 分頁查詢商品
     * @param page
     * @param rows
     * @param saleable
     * @param key
     * @return
     */
    @GetMapping("/spu/page")
    PageResult<SpuBo> querySpuByPage(
            @RequestParam(value = "page", defaultValue = "1") Integer page,
            @RequestParam(value = "rows", defaultValue = "5") Integer rows,
            @RequestParam(value = "saleable", defaultValue = "true") Boolean saleable,
            @RequestParam(value = "key", required = false) String key);

    /**
     * 根據spu商品id查詢詳情
     * @param id
     * @return
     */
    @GetMapping("/spu/detail/{id}")
    SpuDetail querySpuDetailById(@PathVariable("id") Long id);

    /**
     * 根據spu的id查詢sku
     * @param id
     * @return
     */
    @GetMapping("sku/list")
    List<Sku> querySkuBySpuId(@RequestParam("id") Long id);
}

以上的這些代碼直接從商品微服務中拷貝而來,完全一致。差別就是沒有方法的具體實現。大家覺得這樣有沒有問題?

FeignClient代碼遵循SpringMVC的風格,因此與商品微服務的Controller完全一致。這樣就存在一定的問題:

  • 代碼冗余。盡管不用寫實現,只是寫接口,但服務調用方要寫與服務controller一致的代碼,有幾個消費者就要寫幾次。

  • 增加開發成本。調用方還得清楚知道接口的路徑,才能編寫正確的FeignClient。

問題解決:

因此,一種比較友好的實踐是這樣的:

  • 我們的服務提供方不僅提供實體類,還要提供api接口聲明

  • 調用方不用自己編寫接口方法聲明,直接繼承提供方給的Api接口即可,

第一步:服務的提供方在leyou-item-interface中提供API接口,並編寫接口聲明:

注意:商品服務接口Api,api編寫技巧:直接從leyou-item-service2模塊中拷貝所需要的接口方法,返回值不再使用ResponseEntity

 CategoryApi:

package lucky.leyou.item.api;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

@RequestMapping("category")
public interface CategoryApi {

    @GetMapping("names")
    List<String> queryNameByIds(@RequestParam("ids") List<Long> ids);
}

GoodsApi :

package lucky.leyou.item.api;

import lucky.leyou.common.domain.PageResult;
import lucky.leyou.item.bo.SpuBo;
import lucky.leyou.item.domain.Sku;
import lucky.leyou.item.domain.SpuDetail;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * 商品服務的接口:供feign使用
 */public interface GoodsApi {

    /**
     * 分頁查詢商品
     * @param page
     * @param rows
     * @param saleable
     * @param key
     * @return
     */
    @GetMapping(path = "/spu/page")
    public PageResult<SpuBo> querySpuBoByPage(
            @RequestParam(value = "key", required = false)String key,
            @RequestParam(value = "saleable", required = false)Boolean saleable,
            @RequestParam(value = "page", defaultValue = "1")Integer page,
            @RequestParam(value = "rows", defaultValue = "5")Integer rows
    );

    /**
     * 根據spu商品id查詢詳情
     * @param id
     * @return
     */
    @GetMapping("/spu/detail/{id}")
    SpuDetail querySpuDetailById(@PathVariable("id") Long id);

    /**
     * 根據spu的id查詢sku
     * @param id
     * @return
     */
    @GetMapping("sku/list")
    List<Sku> querySkuBySpuId(@RequestParam("id") Long id);
}

BrandApi:

package lucky.leyou.item.api;

import lucky.leyou.item.domain.Brand;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;

@RequestMapping("brand")
public interface BrandApi {

    @GetMapping("{id}")
    public Brand queryBrandById(@PathVariable("id") Long id);
}

SpecificationApi:

package lucky.leyou.item.api;

import lucky.leyou.item.domain.SpecParam;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;

import java.util.List;

/**
 * 規格參數的接口:供feign使用
 */
@RequestMapping("spec")
public interface SpecificationApi {

    @GetMapping("params")
    public List<SpecParam> queryParams(
            @RequestParam(value = "gid", required = false) Long gid,
            @RequestParam(value = "cid", required = false) Long cid,
            @RequestParam(value = "generic", required = false) Boolean generic,
            @RequestParam(value = "searching", required = false) Boolean searching
    );

}

第二步:在調用方leyou-search中編寫FeignClient,但不要寫方法聲明了,直接繼承leyou-item-interface提供的api接口

商品的FeignClient:

package lucky.leyou.client;

import lucky.leyou.item.api.GoodsApi;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient(value = "item-service")
public interface GoodsClient extends GoodsApi {
}

商品分類的FeignClient:

package lucky.leyou.client;

import lucky.leyou.item.api.CategoryApi;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient(value = "item-service")
public interface CategoryClient extends CategoryApi {
}

品牌的FeignClient:

package lucky.leyou.client;

import lucky.leyou.item.api.BrandApi;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient("item-service")
public interface BrandClient extends BrandApi {
}

規格參數的FeignClient:

package lucky.leyou.client;

import lucky.leyou.item.api.SpecificationApi;
import org.springframework.cloud.openfeign.FeignClient;

@FeignClient("item-service")
public interface SpecificationClient extends SpecificationApi {
}

第三步:

在leyou-search中引入springtest依賴:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-test</artifactId>
    <scope>test</scope>
</dependency>

在接口上按快捷鍵:Ctrl + Shift + T

package lucky.leyou.client;

import lucky.leyou.LeyouSearchServiceApplication;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.*;

@RunWith(SpringRunner.class)
@SpringBootTest(classes = LeyouSearchServiceApplication.class)
public class CategoryClientTest {

    @Autowired
    private CategoryClient categoryClient;

    @Test
    public void testQueryCategories() {
        List<String> names = this.categoryClient.queryNameByIds(Arrays.asList(1L, 2L, 3L));
        names.forEach(System.out::println);
    }
}

啟動工程報錯,feign啟動報錯

***************************
APPLICATION FAILED TO START
***************************
Description:

The bean 'pigx-upms-biz.FeignClientSpecification', defined in null, could not be registered. A bean with that name has already been defined in null and overriding is disabled.

Action:

Consider renaming one of the beans or enabling overriding by setting spring.main.allow-bean-definition-overriding=true

Process finished with exit code 1

解決方案:

在模塊的application.yml文件中添加

啟動成功:

執行結果:

總結:在leyou-search這個微服務中通過Feign調用的leyou-item這個微服務

4.導入數據

 

(1)創建GoodsRepository

 

java代碼:

package lucky.leyou.reponsitory;

import lucky.leyou.domain.Goods;
import org.springframework.data.elasticsearch.repository.ElasticsearchRepository;

public interface GoodsRepository extends ElasticsearchRepository<Goods, Long> {
}

(2)導入數據

導入數據其實就是查詢數據,然后把查詢到的Spu轉變為Goods來保存,因此我們先編寫一個SearchService,然后在里面定義一個方法, 把Spu轉為Goods

package lucky.leyou.service;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import lucky.leyou.client.BrandClient;
import lucky.leyou.client.CategoryClient;
import lucky.leyou.client.GoodsClient;
import lucky.leyou.client.SpecificationClient;
import lucky.leyou.domain.Goods;
import lucky.leyou.item.domain.*;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.math.NumberUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.*;

/**
 * 搜索服務
 */
@Service
public class SearchService {

    @Autowired
    private BrandClient brandClient;

    @Autowired
    private CategoryClient categoryClient;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SpecificationClient specificationClient;

    private static final ObjectMapper MAPPER = new ObjectMapper();

    /**
     * 把Spu轉為Goods
     * @param spu
     * @return
     * @throws IOException
     */
    public Goods buildGoods(Spu spu) throws IOException {

        // 創建goods對象
        Goods goods = new Goods();

        // 根據品牌id查詢品牌
        Brand brand = this.brandClient.queryBrandById(spu.getBrandId());

        // 查詢分類名稱,Arrays.asList該方法能將方法所傳參數轉為List集合
        List<String> names = this.categoryClient.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));

        // 根據spuid查詢spu下的所有sku
        List<Sku> skus = this.goodsClient.querySkuBySpuId(spu.getId());
        //初始化一個價格集合,收集所有的sku的價格
        List<Long> prices = new ArrayList<>();
        //收集sku的必要的字段信息
        List<Map<String, Object>> skuMapList = new ArrayList<>();
        // 遍歷skus,獲取價格集合
        skus.forEach(sku ->{
            prices.add(sku.getPrice());
            Map<String, Object> skuMap = new HashMap<>();
            skuMap.put("id", sku.getId());
            skuMap.put("title", sku.getTitle());
            skuMap.put("price", sku.getPrice());
            //獲取sku中的圖片,數據庫中的圖片可能是多張,多張是以,分隔,所以也以逗號進行切割返回圖片數組,獲取第一張圖片
            skuMap.put("image", StringUtils.isNotBlank(sku.getImages()) ? StringUtils.split(sku.getImages(), ",")[0] : "");
            skuMapList.add(skuMap);
        });

        // 以tb_spec_param表中的分類cid字段和searching字段為查詢條件查詢出tb_spec_param表中所有的搜索規格參數
        //將每一個查詢結果封裝成SpecParam這個bean對象中,將bean對象放入map中構成查詢結果集
        List<SpecParam> params = this.specificationClient.queryParams(null, spu.getCid3(), null, true);
        // 根據spuid查詢spuDetail(即數據庫表tb_spu_detail中的一行數據)。獲取規格參數值
        SpuDetail spuDetail = this.goodsClient.querySpuDetailById(spu.getId());
        // 獲取通用的規格參數,利用jackson工具類json轉換為object對象(反序列化),參數1:要轉化的json數據,參數2:要轉換的數據類型格式
        Map<Long, Object> genericSpecMap = MAPPER.readValue(spuDetail.getGenericSpec(), new TypeReference<Map<Long, Object>>() {
        });
        // 獲取特殊的規格參數
        Map<Long, List<Object>> specialSpecMap = MAPPER.readValue(spuDetail.getSpecialSpec(), new TypeReference<Map<Long, List<Object>>>() {
        });
        // 定義map接收{規格參數名,規格參數值}
        Map<String, Object> paramMap = new HashMap<>();
        params.forEach(param -> {
            // 判斷是否通用規格參數
            if (param.getGeneric()) {
                // 獲取通用規格參數值
                String value = genericSpecMap.get(param.getId()).toString();
                // 判斷是否是數值類型
                if (param.getNumeric()){
                    // 如果是數值的話,判斷該數值落在那個區間
                    value = chooseSegment(value, param);
                }
                // 把參數名和值放入結果集中
                paramMap.put(param.getName(), value);
            } else {
                paramMap.put(param.getName(), specialSpecMap.get(param.getId()));
            }
        });

        // 設置參數
        goods.setId(spu.getId());
        goods.setCid1(spu.getCid1());
        goods.setCid2(spu.getCid2());
        goods.setCid3(spu.getCid3());
        goods.setBrandId(spu.getBrandId());
        goods.setCreateTime(spu.getCreateTime());
        goods.setSubTitle(spu.getSubTitle());
        goods.setAll(spu.getTitle() +" "+ StringUtils.join(names, " ")+" "+brand.getName());
        //獲取spu下的所有sku的價格
        goods.setPrice(prices);
        //獲取spu下的所有sku,並使用jackson包下ObjectMapper工具類,將任意的Object對象轉化為json字符串
        goods.setSkus(MAPPER.writeValueAsString(skuMapList));
        //獲取所有的規格參數{name:value}
        goods.setSpecs(paramMap);

        return goods;
    }

    /**
     * 判斷value值所在的區間
     * 范例:value=5.2 Segments:0-4.0,4.0-5.0,5.0-5.5,5.5-6.0,6.0-
     * @param value
     * @param p
     * @return
     */
    private String chooseSegment(String value, SpecParam p) {
        double val = NumberUtils.toDouble(value);
        String result = "其它";
        // 保存數值段
        for (String segment : p.getSegments().split(",")) {
            String[] segs = segment.split("-");
            // 獲取數值范圍
            double begin = NumberUtils.toDouble(segs[0]);
            double end = Double.MAX_VALUE;
            if(segs.length == 2){
                end = NumberUtils.toDouble(segs[1]);
            }
            // 判斷是否在范圍內
            if(val >= begin && val < end){
                if(segs.length == 1){
                    result = segs[0] + p.getUnit() + "以上";
                }else if(begin == 0){
                    result = segs[1] + p.getUnit() + "以下";
                }else{
                    result = segment + p.getUnit();
                }
                break;
            }
        }
        return result;
    }

}

然后編寫一個測試類,循環查詢Spu,然后調用IndexService中的方法,把SPU變為Goods,然后寫入索引庫:

package lucky.leyou.elasticsearch;

import lucky.leyou.client.GoodsClient;
import lucky.leyou.common.domain.PageResult;
import lucky.leyou.domain.Goods;
import lucky.leyou.item.bo.SpuBo;
import lucky.leyou.reponsitory.GoodsRepository;
import lucky.leyou.service.SearchService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.test.context.junit4.SpringRunner;

import java.io.IOException;
import java.util.List;
import java.util.stream.Collectors;

@SpringBootTest
@RunWith(SpringRunner.class)
public class ElasticSearchTest1 {
    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private GoodsRepository goodsRepository;

    @Autowired
    private GoodsClient goodsClient;

    @Autowired
    private SearchService searchService;

    @Test
    public void test(){
        //創建索引,會根據Goods類的@Document注解信息來創建
        this.elasticsearchTemplate.createIndex(Goods.class);
        //配置映射,會根據Goods類中的id、Field等字段來自動完成映射
        elasticsearchTemplate.putMapping(Goods.class);

        Integer page = 1;
        Integer rows = 100;

        do {
            // 分頁查詢spuBo,獲取分頁結果集
            PageResult<SpuBo> pageResult = this.goodsClient.querySpuBoByPage(null, null, page, rows);
            // 遍歷spubo集合轉化為List<Goods>
            List<Goods> goodsList = pageResult.getItems().stream().map(spuBo -> {
                try {
                    return this.searchService.buildGoods(spuBo);
                } catch (IOException e) {
                    e.printStackTrace();
                }
                return null;
            }).collect(Collectors.toList());

            //執行新增數據的方法
            this.goodsRepository.saveAll(goodsList);

            // 獲取當前頁的數據條數,如果是最后一頁,沒有100條
            rows = pageResult.getItems().size();
            // 每次循環頁碼加1
            page++;
        } while (rows == 100);

    }
}

執行后

打開postman進行測試,輸入http://localhost:9200/goods/_search

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM