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

  • 相关阅读:
    LeetCode 32. 最长有效括号(Longest Valid Parentheses)
    LeetCode 141. 环形链表(Linked List Cycle)
    LeetCode 160. 相交链表(Intersection of Two Linked Lists)
    LeetCode 112. 路径总和(Path Sum)
    LeetCode 124. 二叉树中的最大路径和(Binary Tree Maximum Path Sum)
    LightGBM新特性总结
    sql service 事务与锁
    C#泛型实例详解
    C# 中的委托和事件(详解)
    C# DateTime日期格式化
  • 原文地址:https://www.cnblogs.com/luckyplj/p/11603523.html
Copyright © 2011-2022 走看看