zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    MS SQL Server迁移至Azure SQL
    Aras Innovator 11 sp2 firefox客户端设置
    Aras Innovator 11 sp2 IE客户端设置
    Aras Innovator 11 sp2安装
    JDK Windows安装
    mocha测试es6问题
    jQuery中animate与scrollTop、offset().top实例
    AI下载步骤
    Visual Studio Code必备插件
    Visual Studio code快捷键
  • 原文地址:https://www.cnblogs.com/luckyplj/p/11603523.html
Copyright © 2011-2022 走看看