一、商品检索功能介绍
1.1 检索两个入口
1.2 检索列表展示页面
1.3 根据业务搭建数据结构
1.3.1 建立mapping!
这时我们要思考三个问题:
1、 哪些字段需要分词
a) 例如:商品名称
2、 我们用哪些字段进行过滤
a) 平台属性值
b) 分类Id
3、 哪些字段我们需要通过搜索查询出来。
a) 商品名称,价格,图片等。
以上分析的所有显示,以及分词,过滤的字段都应该在es中出现。Es中如何保存这些数据呢?
“根据上述的字段描述,应该建立一个mappings对应的存上上述字段描述的信息!”
根据以上制定出如下结构:mappings
Index:goodstype:_doc
document: properties - rows
field: id,price,title…
Es中index默认是true。
注意:ik_max_word 中文词库必须有!
attrs:平台属性值的集合,主要用于平台属性值过滤。
1.3.2 nested ``介绍
nested:类型是一种特殊的对象object数据类型(specialised version of the object datatype ),允许对象数组彼此独立地进行索引和查询。
demo: 建立一个普通的index
如果linux 中有这个my_index 先删除!DELETE /my_index
步骤1:建立一个index PUT my_index/_doc/1 { “group” : “fans”, “user” : [ { “first” : “John”, “last” : “Smith” }, { “first” : “Alice”, “last” : “White” } ] } |
---|
步骤2 : 执行查询 GET my_index/_search { “query”: { “bool”: { “must”: [ { “match”: { “user.first”: “Alice” }}, { “match”: { “user.last”: “Smith” }} ] } } } 查询结果: 能够查询出数据的原因是因为:建立my_index 的时候,它默认的数据类型是Object {user.first:”John ,Alice”} {user.last:”Smith,White”} 实际上:从业务的角度出发应该没有数据: 因为 User1 {John,Smith} User2 {Alice,White} 是两个对象 而 {Alice,Smith} 在当前的user 中不存在! |
步骤3:删除当前索引 DELETE /my_index |
步骤4:建立一个nested 类型的 PUT my_index { “mappings”: { “properties”: { “user”: { “type”: “nested” } } } } user 字段映射为nested 类型,而不是默认的object 类型 |
重新执行步骤1,使用nested 查询 GET /my_index/_search { “query”: { “nested”: { “path”: “user”, “query”: { “bool”: { “must”: [ {“match”: {“user.first”: “Alice”}}, {“match”: {“user.last”: “Smith”}} ] } } } } } 此查询得不到匹配,是因为 Alice 和Smith 不在同一个嵌套对象中。{“match”: {“user.last”: “White”}} 此时就会有数据: |
1.4 搭建service-list服务
在service模块下搭建,搭建方式如service-item
1.4.1 修改配置pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd” > <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.atguigu.gmall</groupId> <artifactId>service</artifactId> <version>1.0</version> </parent> <artifactId>service-list</artifactId> <version>1.0</version> <packaging>jar</packaging> <name>service-list</name> <description>service-list</description> <dependencies> <dependency> <groupId>com.atguigu.gmall</groupId> <artifactId>service-product-client</artifactId> <version>1.0</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-elasticsearch</artifactId> </dependency> </dependencies> </project> |
---|
说明:
1,引入service-product-client模块
2,引入spring-boot-starter-data-elasticsearch依赖
1.4.2 添加配置文件
bootstrap.properties
spring.application.name=service-list spring.profiles.active=dev spring.cloud.nacos.discovery.server-addr=192.168.200.129:8848 spring.cloud.nacos.config.server-addr=192.168.200.129:8848 spring.cloud.nacos.config.prefix=${spring.application.name} spring.cloud.nacos.config.file-extension=yaml spring.cloud.nacos.config.shared-configs[0].data-id=common.yaml |
---|
1.4.3 构建实体与es mapping建立映射关系
package com.atguigu.gmall.model.list; @Document(indexName = “goods”, shards = 3, replicas = 1) @Data public class Goods { @Id private Long id; @Field(type = FieldType.Keyword, index = false) private String defaultImg; @Field(type = FieldType.Text, analyzer = “ik_max_word”) private String title; @Field(type = FieldType.Double) private Double price; @Field(type = FieldType.Date,format = DateFormat.custom, pattern = “yyyy-MM-dd HH:mm:ss”) private Date createTime; // 新品 @Field(type = FieldType.Long) private Long tmId; @Field(type = FieldType.Keyword) private String tmName; @Field(type = FieldType.Keyword)private String tmLogoUrl; @Field(type = FieldType.Long) private Long category1Id; @Field(type = FieldType.Keyword) private String category1Name; @Field(type = FieldType.Long) private Long category2Id; @Field(type = FieldType.Keyword) private String category2Name; @Field(type = FieldType.Long) private Long category3Id; @Field(type = FieldType.Keyword) private String category3Name; @Field(type = FieldType.Long) private Long hotScore = 0L; @Field(type = FieldType.Nested) private List } |
---|
平台属性-平台属性值@Data public class SearchAttr { @Field(type = FieldType.Long) private Long attrId; @Field(type = FieldType.Keyword) private String attrName; @Field(type = FieldType.Keyword) private String attrValue; } |
1.4.4 初始化mapping结构到es中
package com.atguigu.gmall.list.controller;@RestController @RequestMapping(“api/list”) public class ListApiController { @Autowired private ElasticsearchRestTemplate restTemplate; / * @return * / @GetMapping(“inner/createIndex”) public Result createIndex() { restTemplate.createIndex(Goods.class); restTemplate.putMapping(Goods.class); return Result.ok(); } } |
---|
添加主启动类
| @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)@ComponentScan({“com.atguigu.gmall”})@EnableDiscoveryClient
@EnableFeignClients(basePackages= {“com.atguigu.gmall”})public class ServiceListApplication {
public static void main(String[] args) {
SpringApplication._run_(ServiceListApplication.**class**,args);<br /> }<br />}<br /> |
| —- |
在浏览器运行:
http://localhost:8203/api/list/inner/createIndex
通过kibana查看mapping
重点看:attrs 数据类型必须是nested !
二、商品上架,下架
构建goods数据模型分析
Sku基本信息(详情业务已封装了接口)
Sku分类信息(详情业务已封装了接口)
Sku的品牌信息(无)
Sku对应的平台属性(详情业务已封装了接口)
2.1 在service-product封装接口
2.1.1 Sku的品牌接口
ManageService / 通过品牌Id 来查询数据 @param **tmId __ _ @return ** **/ _BaseTrademark getTrademarkByTmId(Long tmId); |
---|
实现类 @Autowired private BaseTrademarkMapper baseTrademarkMapper; @Override public BaseTrademark getTrademarkByTmId(Long tmId) { return baseTrademarkMapper.selectById(tmId); } |
ProductApiController/ 通过品牌Id 集合来查询数据 @param **tmId __ @return ** **/ @GetMapping(“inner/getTrademark/{tmId}”) public BaseTrademark getTrademark(@PathVariable(“tmId”)Long tmId){ return manageService.getTrademarkByTmId(tmId); } |
2.2 在service-product-client添加接口
ProductFeignClient/ 通过品牌Id 集合来查询数据 @param **tmId __ @return ** **/ @GetMapping(“/api/product/inner/getTrademark/{tmId}”) BaseTrademark getTrademark(@PathVariable(“tmId”)Long tmId); |
---|
ProductDegradeFeignClient @Override public BaseTrademark getTrademark(Long tmId) { return null; } |
2.3 实现商品上架,下架功能
2.3.1 封装接口
package com.atguigu.gmall.list.service; public interface SearchService { / 上架商品列表 @param **skuId __ */ void upperGoods(Long skuId); / 下架商品列表 @param **skuId __ */ void lowerGoods(Long skuId); } |
---|
2.3.2 制作操作es的接口
package com.atguigu.gmall.list.repository; import com.atguigu.gmall.model.list.Goods;import org.springframework.data.elasticsearch.repository.ElasticsearchRepository; public interface GoodsRepository extends ElasticsearchRepository } |
---|
2.3.3 接口实现类
| 实现类
package com.atguigu.gmall.list.service.impl;
@Service
public class SearchServiceImpl implements SearchService {
@Autowiredprivate ProductFeignClient productFeignClient;
@Autowired
private GoodsRepository goodsRepository;
/
上架商品列表 @param **skuId
__ */
@Override
public void upperGoods(Long skuId) {
Goods goods = new Goods();
//查询sku对应的平台属性 _List
if(null != baseAttrInfoList) {
List
SearchAttr searchAttr = new SearchAttr();
searchAttr.setAttrId(baseAttrInfo.getId());
searchAttr.setAttrName(baseAttrInfo.getAttrName());
searchAttr.setAttrValue(baseAttrValueList.get(0).getValueName());
return searchAttr;
}).collect(Collectors._toList
goods.setAttrs(searchAttrList);
}
//查询sku信息 _SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
// 查询品牌 BaseTrademark baseTrademark = productFeignClient.getTrademark(skuInfo.getTmId());
if (baseTrademark != null){
goods.setTmId(skuInfo.getTmId());
goods.setTmName(baseTrademark.getTmName());
goods.setTmLogoUrl(trademark.getLogoUrl());
}
// 查询分类 BaseCategoryView baseCategoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
if (baseCategoryView != null) {
goods.setCategory1Id(baseCategoryView.getCategory1Id());
goods.setCategory1Name(baseCategoryView.getCategory1Name());
goods.setCategory2Id(baseCategoryView.getCategory2Id());
goods.setCategory2Name(baseCategoryView.getCategory2Name());
goods.setCategory3Id(baseCategoryView.getCategory3Id());
goods.setCategory3Name(baseCategoryView.getCategory3Name());
}
goods.setDefaultImg(skuInfo.getSkuDefaultImg());
goods.setPrice(skuInfo.getPrice().doubleValue());
goods.setId(skuInfo.getId());
goods.setTitle(skuInfo.getSkuName());
goods.setCreateTime(new Date());
this.goodsRepository.save(goods);
}
/
下架商品列表 @param _skuId
__ */
@Override
public void lowerGoods(Long skuId) {
this.goodsRepository**.deleteById(skuId);
}
} |
| —- |
2.3.4 控制器
package com.atguigu.gmall.list.controller; /* 商品搜索列表接口 / @RestController @RequestMapping(“api/list”) public class ListApiController { @Autowired private SearchService searchService; @Autowired private ElasticsearchRestTemplate restTemplate; / 上架商品 @param **skuId __ @return ** **/ @GetMapping(“inner/upperGoods/{skuId}”) public Result upperGoods(@PathVariable(“skuId”) Long skuId) { searchService.upperGoods(skuId); return Result.ok(); } / 下架商品 @param **skuId __ @return ** **/ @GetMapping(“inner/lowerGoods/{skuId}”) public Result lowerGoods(@PathVariable(“skuId”) Long skuId) { searchService.lowerGoods(skuId); return Result.ok(); } } |
---|
添加数据
通过kibana查看数据
说明:后期学习了mq,我们可以根据后台系统添加和修改等操作,发送mq消息自动上下架商品
http://localhost:8203/api/list/inner/upperGoods/10
http://localhost:8203/api/list/inner/lowerGoods/10
三、商品热度排名
搜索商品时,后面我们会根据热点排序,何时更新热点?我们在获取商品详情时调用更新
3.1 封装接口与实现类与控制器
SearchService/ 更新热点 @param **skuId __ */ void incrHotScore(Long skuId); |
---|
实现类 @Autowiredprivate RedisTemplate redisTemplate; @Override public void incrHotScore(Long skuId) { // 定义key _String hotKey = “hotScore”; // 保存数据 Double hotScore = redisTemplate.opsForZSet().incrementScore(hotKey, “skuId:” + skuId, 1); if (hotScore%10==0){ // 更新es Optional Goods goods = optional.get(); goods.setHotScore(Math._round goodsRepository.save(goods); } } |
控制器
ListApiController/ 更新商品incrHotScore * @param **skuId __ @return ** **/ @GetMapping(“inner/incrHotScore/{skuId}”) public Result incrHotScore(@PathVariable(“skuId”) Long skuId) { // 调用服务层 searchService.incrHotScore(skuId); return Result.ok(); } |
---|
3.2 在service-list-client封装接口
3.2.1 搭建service-list-client
3.2.2 修改pom.xml
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd” > <modelVersion>4.0.0</modelVersion> <parent> <groupId>com.atguigu.gmall</groupId> <artifactId>service-client</artifactId> <version>1.0</version> </parent> <artifactId>service-list-client</artifactId> <version>1.0</version> <packaging>jar</packaging> <name>service-list-client</name> <description>service-list-client</description> </project> |
---|
3.2.3 添加接口
package com.atguigu.gmall.list.client; @FeignClient(value = “service-list”, fallback = ListDegradeFeignClient.class) public interface ListFeignClient { / 更新商品incrHotScore @param **skuId __ @return ** **/ @GetMapping(“/api/list/inner/incrHotScore/{skuId}”) Result incrHotScore(@PathVariable(“skuId”) Long skuId); } |
---|
package com.atguigu.gmall.list.client.impl; @Component public class ListDegradeFeignClient implements ListFeignClient { @Override public Result incrHotScore(Long skuId) { return null; } } |
3.3 在service-item模块调用接口
引入依赖
<dependency>
<groupId>com.atguigu.gmall</groupId>
<artifactId>service-list-client</artifactId>
<version>1.0</version>
</dependency>
接口调用
@Service public class ItemServiceImpl implements ItemService { @Autowired private ProductFeignClient productFeignClient; @Autowired private ListFeignClient listFeignClient; @Autowired private ThreadPoolExecutor threadPoolExecutor; @Override public Map Map … //获取分类信息 _CompletableFuture BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id()); }, threadPoolExecutor); //更新商品incrHotScore CompletableFuture listFeignClient.incrHotScore(skuId); }, threadPoolExecutor); CompletableFuture.allOf(skuCompletableFuture, spuSaleAttrCompletableFuture, skuValueIdsMapCompletableFuture,skuPriceCompletableFuture, categoryViewCompletableFuture, incrHotScoreCompletableFuture).join(); return result; } } |
---|