学习了Elasticsearch的基本应用之后,我们可以搭建搜索微服务,实现项目里的搜索功能 。
目录
一、搭建搜索微服务
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 {
}