乐优商城学习Day07:
注意:此次代码都是在第六天的基础上
第六天的链接如下:
https://blog.csdn.net/zcylxzyh/article/details/99943700
此次笔记内容主要为:
1.elasticsearch复杂查询,聚合
2.搭建搜索微服务
3.搜索的页面渲染
下面开始第七天的学习:
1.elasticsearch复杂查询,聚合
复杂玩法:
Elasticsearch的原生查询:
在昨天的es-demo中的EsTest中加入:
@Test
public void testQuery(){
//创建查询构建器
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
//结果过滤
queryBuilder.withSourceFilter(new FetchSourceFilter(new String[]{
"id","title","price"},null));
//添加查询条件
queryBuilder.withQuery(QueryBuilders.matchQuery("title","小米手机"));
//排序
queryBuilder.withSort(SortBuilders.fieldSort("price").order(SortOrder.DESC));
//分页
queryBuilder.withPageable(PageRequest.of(0,2));
Page<Item> result = repository.search(queryBuilder.build());
long total = result.getTotalElements();
System.out.println("total = " + total);
int totalPages = result.getTotalPages();
System.out.println("totalPages = " + totalPages);
List<Item> list = result.getContent();
for (Item item : list) {
System.out.println("item = " + item);
}
}
结果:
聚合:
继续加入:
//聚合
@Test
public void testAgg(){
NativeSearchQueryBuilder queryBuilder = new NativeSearchQueryBuilder();
String aggName = "popularBrand";
//聚合
queryBuilder.addAggregation(AggregationBuilders.terms(aggName).field("brand"));
//查询并返回待聚合结果
AggregatedPage<Item> result = template.queryForPage(queryBuilder.build(), Item.class);
//解析聚合
Aggregations aggs = result.getAggregations();
//获取指定名称的聚合
StringTerms terms = aggs.get(aggName);
//获取桶
List<StringTerms.Bucket> buckets = terms.getBuckets();
for (StringTerms.Bucket bucket : buckets) {
System.out.println("bucket.getKeyAsString() = " + bucket.getKeyAsString());
System.out.println("bucket.getDocCount() = " + bucket.getDocCount());
}
}
结果:
第一部分结束。
2.搭建搜索微服务
2.1 搜索微服务
首先创建出搜索微服务:
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>com.leyou.parent</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.service</groupId>
<artifactId>ly-search</artifactId>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-elasticsearch</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-openfeign</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>com.leyou.service</groupId>
<artifactId>ly-item-interface</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
配置文件:
server:
port: 8083
spring:
application:
name: search-service
data:
elasticsearch:
cluster-name: elasticsearch
cluster-nodes: 192.168.133.128:9300
jackson:
default-property-inclusion: non_null
eureka:
client:
service-url:
defaultZone: http://127.0.0.1:10086/eureka
registry-fetch-interval-seconds: 5
instance:
prefer-ip-address: true
ip-address: 127.0.0.1
启动类:
package com.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
public class LySearchApplication {
public static void main(String[] args) {
SpringApplication.run(LySearchApplication.class, args);
}
}
2.2 索引库
接下来,我们需要商品数据导入索引库,便于用户搜索。
那么问题来了,我们有SPU和SKU,到底如何保存到索引库?
再来看看页面中有什么数据:
最终的数据结构:
我们创建一个类,封装要保存到索引库的数据,并设置映射属性
Goods:
package com.leyou.search.pojo;
import lombok.Data;
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.Map;
import java.util.Set;
@Data
@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 Set<Long> price;// 价格
@Field(type = FieldType.Keyword, index = false)
private String skus;// sku信息的json结构
private Map<String, Object> specs;// 可搜索的规格参数,key是参数名,值是参数值
}
索引库中的数据来自于数据库,我们不能直接去查询商品的数据库,因为真实开发中,每个微服务都是相互独立的,包括数据库也是一样。所以我们只能调用商品微服务提供的接口服务。
先思考我们需要的数据:
- SPU信息
- SKU信息
- SPU的详情
- 商品分类名称(拼接all字段)
再思考我们需要哪些服务:
- 第一:分批查询spu的服务,已经写过。
- 第二:根据spuId查询sku的服务,已经写过
- 第三:根据spuId查询SpuDetail的服务,已经写过
- 第四:根据商品分类id,查询商品分类名称,没写过
- 第五:根据商品品牌id,查询商品的品牌,没写过
因此我们需要额外提供一个查询商品分类名称的接口。
在CategoryController中添加:
//根据id查询商品分类
@GetMapping("list/ids")
public ResponseEntity<List<Category>> queryCategoryByIds(@RequestParam("ids")List<Long> ids){
return ResponseEntity.ok(categoryService.queryByIds(ids));
}
对应的service:
public List<Category> queryByIds(List<Long> ids){
List<Category> list = categoryMapper.selectByIdList(ids);
if (CollectionUtils.isEmpty(list)) {
//返回404
throw new LyException(ExceptionEnum.CATEGORY_NOT_FOND);
}
return list;
}
BrandController中添加:
//根据id查询品牌
@GetMapping("{id}")
public ResponseEntity<Brand> queryBrandById(@PathVariable("id") Long id){
return ResponseEntity.ok(brandService.queryById(id));
}
对应的service
public Brand queryById(Long id){
Brand brand = brandMapper.selectByPrimaryKey(id);
if(brand ==null){
throw new LyException(ExceptionEnum.BRAND_NOT_FOUND);
}
return brand;
}
然后:操作leyou-search工程
现在,我们要在搜索微服务调用商品微服务的接口。
方法:
- 我们的服务提供方不仅提供实体类,还要提供api接口声明
- 调用方不用字自己编写接口方法声明,直接继承提供方给的Api接口即可,
第一步:服务的提供方在leyou-item-interface中提供API接口,并编写接口声明:需要引入springMVC及leyou-common的依赖:
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>ly-item</artifactId>
<groupId>com.leyou.service</groupId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>com.leyou.service</groupId>
<artifactId>ly-item-interface</artifactId>
<dependencies>
<dependency>
<groupId>tk.mybatis</groupId>
<artifactId>mapper-core</artifactId>
<version>1.0.4</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<version>2.9.6</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
</dependency>
<dependency>
<groupId>com.leyou.common</groupId>
<artifactId>ly-common</artifactId>
<version>1.0.0-SNAPSHOT</version>
</dependency>
</dependencies>
</project>
BrandApi:
package com.leyou.item.api;
import com.leyou.item.pojo.Brand;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
public interface BrandApi {
@GetMapping("brand/{id}")
Brand queryBrandById(@PathVariable("id") Long id);
}
CategoryApi:
package com.leyou.item.api;
import com.leyou.item.pojo.Category;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
public interface CategoryApi {
@GetMapping("category/list/ids")
List<Category> queryCategoryByIds(@RequestParam("ids")List<Long> ids);
}
GoodsApi :
package com.leyou.item.api;
import com.leyou.common.vo.PageResult;
import com.leyou.item.pojo.Sku;
import com.leyou.item.pojo.Spu;
import com.leyou.item.pojo.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.RequestParam;
import java.util.List;
public interface GoodsApi {
//根据spu查询下面的所有sku
@GetMapping("sku/list")
List<Sku> querySkuBySpuId(@RequestParam("id") Long spuId);
@GetMapping("/spu/page")
PageResult<Spu> querySpuByPage(
@RequestParam(value = "page",defaultValue = "1") Integer page,
@RequestParam(value = "rows",defaultValue = "5") Integer rows,
@RequestParam(value = "saleable",required = false) Boolean saleable,
@RequestParam(value = "key",required = false) String key
);
//根据spu的id查询详情detail
@GetMapping("/spu/detail/{id}")
SpuDetail queryDetailById(@PathVariable("id") Long spuId);
}
SpecificationApi:
package com.leyou.item.api;
import com.leyou.item.pojo.SpecParam;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import java.util.List;
public interface SpecificationApi {
@GetMapping("spec/params")
List<SpecParam> queryParamList(
@RequestParam(value = "gid", required = false) Long gid,
@RequestParam(value = "cid", required = false) Long cid,
@RequestParam(value = "searching", required = false) Boolean searching
);
}
第二步:在调用方leyou-search中编写FeignClient,但不要写方法声明了,直接继承leyou-item-interface提供的api接口:
BrandClient
package com.leyou.search.client;
import com.leyou.item.api.BrandApi;
import org.springframework.cloud.openfeign.FeignClient;
@FeignClie