学习了Elasticsearch的基本应用之后,我们可以搭建搜索微服务,实现项目里的搜索功能 。

目录

 

一、搭建搜索微服务

二、索引库数据格式分析

三、添加client接口调用商品微服务接口


 

一、搭建搜索微服务

1.创建模块

 

2.引入依赖

<?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>com.leyou.parent</groupId>
        <version>1.0.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.leyou.search</groupId>
    <artifactId>leyou-search</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <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>
        <!-- eureka -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <!-- feign -->
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <!--商品微服务-->
        <dependency>
            <groupId>com.leyou.item</groupId>
            <artifactId>leyou-item-interface</artifactId>
            <version>${leyou.latest.version}</version>
        </dependency>
        <dependency>
            <groupId>com.leyou.common</groupId>
            <artifactId>leyou-common</artifactId>
            <version>1.0.0-SNAPSHOT</version>
        </dependency>
        <!--测试-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
        </dependency>
    </dependencies>
    
</project>

 

3.添加并修改配置文件

server:
  port: 8083
  spring:
spring:
  application:
    name: search-service
  data:
    elasticsearch:
      cluster-name: elasticsearch
      cluster-nodes: 47.102.131.121:9300
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:10086/eureka
    registry-fetch-interval-seconds: 10 #每10秒拉取一次
  instance:
    lease-renewal-interval-in-seconds: 5 # 心跳时间:5秒钟发送一次心跳
    lease-expiration-duration-in-seconds: 15 # 过期时间:10秒不发送就过期b

 

4.添加引导类

@SpringBootApplication
@EnableDiscoveryClient
@EnableFeignClients
public class LeyouSearchApplication {
    public static void main(String[] args) {
        SpringApplication.run(LeyouSearchApplication.class);
    }
}

 

二、索引库数据格式分析

 

1.用结果来分析

可以看到,每一个搜索结果都有至少1个商品,当我们选择大图下方的小图,商品会跟着变化。

因此,搜索的结果是SPU,即多个SKU的集合

既然搜索的结果是SPU,那么我们索引库中存储的应该也是SPU,但是却需要包含SKU的信息。

 

2.需要什么数据

能看到:图片、价格、标题、副标题

隐藏的数据:spu的id,sku的id

还有,过滤条件

 

过滤条件也要存储到索引库中,包括:商品分类、品牌、以及其他可用来搜索的规格参数等 。

 

所以,我们需要的数据有:spuId、SkuId、商品分类id、品牌id、图片、价格、商品的创建时间、sku信息集、可搜索的规格参数。

 

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;

@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;// 卖点
    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是参数名,值是参数值

    记得生成getter、setter方法
}

类中特殊字段的解释:

  • 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:红色

 

三、添加client接口调用商品微服务接口

 

虽然索引库中的数据来自于数据库,但我不能直接去查询商品的数据库。

在开发中,每个微服务都是相互独立的,包括数据库也是一样。

所以我们只能调用商品微服务提供的接口服务。

 

由索引库数据格式我们可以知道所需要的数据:

  • SPU信息

  • SKU信息

  • SPU的详情

  • 商品分类名称(拼接all字段)

  • 品牌名称

  • 规格参数

那么我们需要的服务:

  • 第一:分批查询spu的服务,已经写过。

  • 第二:根据spuId查询sku的服务,已经写过

  • 第三:根据spuId查询SpuDetail的服务,已经写过

  • 第四:根据商品分类id,查询商品分类名称,没写过

  • 第五:根据商品品牌id,查询商品的品牌,没写过

  • 第六:规格参数接口

所以,我们去实现没写过的服务。

 

根据商品品牌id,查询商品的品牌

controller:

    @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);
    }

service:

 

 

商品分类名称查询的方法:

 /**
     * 查询商品分类名称
     * @param ids
     * @return
     */
    @GetMapping("names")
    public ResponseEntity<List<String>> queryNamesByIds(@RequestParam("ids")List<Long> ids){

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

 

重启leyou-item服务,测试一下看看:

 

然后我们要在搜索微服务调用商品微服务的接口。

使用Feign,编写FeignClient。

 

在此之前,我们的服务提供方不仅要提供实体类,还要提供api接口声明。

这样,调用方不用编写接口方法声明,直接继承提供方给的接口就行了。

我们去服务提供方里的leyou-item-interface中提供api接口。

需要在leyou-item-interface中引入SpringMVC以及leyou-common的依赖:

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.0.6.RELEASE</version>
</dependency>
<dependency>
    <groupId>com.leyou.common</groupId>
    <artifactId>leyou-common</artifactId>
    <version>1.0.0-SNAPSHOT</version>
</dependency>

编写接口:

@RequestMapping("brand")
public interface BrandApi {

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

}
@RequestMapping("category")
public interface CategoryApi {

    /**
     * 查询商品分类名称
     * @param ids
     * @return
     */
    @GetMapping("names")
    public List<String> queryNamesByIds(@RequestParam("ids")List<Long> ids);

}
public interface GoodsApi {

    /**
     * 根据分页条件查询spu
     * @param key
     * @param saleable
     * @param page
     * @param rows
     * @return
     */
    @GetMapping("spu/page")
    public PageResult<SpuBo> querySpuBoByPage(
            @RequestParam(value = "key",required = false)String key,
            @RequestParam(value = "saleable",required = false)Boolean saleable,
            @RequestParam(value = "page",required = false)Integer page,
            @RequestParam(value = "rows",required = false)Integer rows
    );


    /**
     * 根据spuId查询spuDetail
     * @param spuId
     * @return
     */
    @GetMapping("spu/detail/{spuId}")
    public SpuDetail querySpuDetailBySpuId(@PathVariable("spuId")Long spuId);

    /**
     * 根据spuId查询sku的集合
     * @param spuId
     * @return
     */
    @GetMapping("sku/list")
    public List<Sku> querySkusBySpuId(@RequestParam("id")Long spuId);


}
@RequestMapping("spec")
public interface SpecificationApi {
    /**
     * 根据条件查询规格参数
     * @param gid
     * @return
     */
    @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,并继承服务提供方提供的相应的api接口。

@FeignClient(value = "item-service")
public interface GoodsClient extends GoodsApi {
}
@FeignClient(value = "item-service")
public interface CategoryClient extends CategoryApi {
}
@FeignClient("item-service")
public interface BrandClient extends BrandApi {
}
@FeignClient("item-service")
public interface SpecificationClient extends SpecificationApi {
}