学习目标:
无论PC端/移动端,根据用户输入的检索条件,查询出对用的商品
首页的分类,按照三级分类ID进行查询
搜索栏,用户录入任意购买意向商品关键字进行检索
MySQL | ElasticSearch | |
---|---|---|
数据库 | database | index(索引库) |
表 | table | type(已废弃) |
字段约束 | schema | mapping映射 |
行 | row | Document(文档) |
字段 | column | Field(域) |
设计索引库方法:
这时我们要思考三个问题:
哪些字段需要分词
我们用哪些字段进行过滤(过滤项)
平台属性值
分类Id
品牌Id
哪些字段我们需要通过搜索查询出来(业务数据)。
以上分析的所有显示,以及分词,过滤的字段都应该在es中出现。Es中如何保存这些数据呢?
根据上述的字段描述,应该建立一个mappings对应的存上上述字段描述的信息!
根据以上制定出如下结构:mappings
Index:goods
document: properties
field: id,price,title…
ES中index默认是true。
注意:ik_max_word 中文词库必须有!
attrs:平台属性值的集合,主要用于平台属性值过滤。
字符串类型ES中两种:
nested:类型是一种特殊的对象object数据类型(specialised version of the object datatype ),允许对象数组彼此独立地进行索引和查询。
demo: 建立一个普通的index
如果linux 中有这个my_comment_index 先删除!DELETE /my_comment_index
步骤1:建立一个索引( 存储博客文章及其所有评论)
PUT my_comment_index/_doc/1
{
"title": "狂人日记",
"body": "《狂人日记》是一篇象征性和寓意很强的小说,当时,鲁迅对中国国民精神的麻木愚昧颇感痛切。",
"comments": [
{
"name": "张三",
"age": 34,
"rating": 8,
"comment": "非常棒的文章",
"commented_on": "30 Nov 2023"
},
{
"name": "李四",
"age": 38,
"rating": 9,
"comment": "文章非常好",
"commented_on": "25 Nov 2022"
},
{
"name": "王五",
"age": 33,
"rating": 7,
"comment": "手动点赞",
"commented_on": "20 Nov 2021"
}
]
}
#类比项目中商品信息
PUT goods/_doc/1
{
"title": "HUAWEI Mate 50 直屏旗舰 超光变XMAGE影像 北斗卫星消息 低电量应急模式 128GB冰霜银华为鸿蒙手机",
"attrInfo": [
{
"id": 23,
"attrName": "运行内存",
"attrValue": "8G"
},
{
"id": 114,
"attrName": "CPU型号",
"attrValue": "骁龙8+ Gen 1"
}
]
}
如上所示,所以我们有一个文档描述了一个帖子和一个包含帖子上所有评论的内部对象评论。 但是Elasticsearch搜索中的内部对象并不像我们期望的那样工作。
步骤2 : 执行查询
GET /my_comment_index/_search
{
"query": {
"bool": {
"must": [
{
"match": {
"comments.name": "李四"
}
},
{
"match": {
"comments.age": 34
}
}
]
}
}
}
查询结果:居然正常的响应结果了
原因分析:comments字段默认的数据类型是Object,故我们的文档内部存储为:
{ "title": [ 狂人日记], "body": [ 《狂人日记》是一篇象征性和寓意很强的小说,当时... ], "comments.name": [ 张三, 李四, 王五 ], "comments.comment": [ 非常棒的文章,文章非常好,王五,... ], "comments.age": [ 33, 34, 38 ], "comments.rating": [ 7, 8, 9 ] }
{
"title": "HUAWEI Mate 50 直屏旗舰 超光变XMAGE影像 北斗卫星消息 低电量应急模式 128GB冰霜银华为鸿蒙手机",
"attrInfo.id":[23,114],
"attrInfo.attrName":["运行内存","CPU型号"],
"attrInfo.attrValue":["8G","CPU型号","骁龙8+ Gen 1"],
}
我们可以清楚地看到,comments.name和comments.age之间的关系已丢失。这就是为什么我们的文档匹配李四和34的查询。
步骤3:删除当前索引
DELETE /my_comment_index
步骤4:建立一个nested 类型的(comments字段映射为nested类型,而不是默认的object类型)
PUT my_comment_index
{
"mappings": {
"properties": {
"comments": {
"type": "nested"
}
}
}
}
PUT my_comment_index/_doc/1
{
"title": "狂人日记",
"body": "《狂人日记》是一篇象征性和寓意很强的小说,当时,鲁迅对中国国民精神的麻木愚昧颇感痛切。",
"comments": [
{
"name": "张三",
"age": 34,
"rating": 8,
"comment": "非常棒的文章",
"commented_on": "30 Nov 2023"
},
{
"name": "李四",
"age": 38,
"rating": 9,
"comment": "文章非常好",
"commented_on": "25 Nov 2022"
},
{
"name": "王五",
"age": 33,
"rating": 7,
"comment": "手动点赞",
"commented_on": "20 Nov 2021"
}
]
}
重新执行步骤1,使用nested 查询
GET /my_comment_index/_search
{
"query": {
"nested": {
"path": "comments",
"query": {
"bool": {
"must": [
{
"match": {
"comments.name": "李四"
}
},
{
"match": {
"comments.age": 34
}
}
]
}
}
}
}
}
结果发现没有返回任何的文档,这是何故?
当将字段设置为nested 嵌套对象将数组中的每个对象索引为单独的隐藏文档,这意味着可以独立于其他对象查询每个嵌套对象。文档的内部表示:
{ {
"comments.name": [ 张三], "comments.comment": [ 非常棒的文章 ], "comments.age": [ 34 ], "comments.rating": [ 9 ]
}, {
"comments.name": [ 李四], "comments.comment": [ 文章非常好 ], "comments.age": [ 38 ], "comments.rating": [ 8 ]
}, {
"comments.name": [ 王五], "comments.comment": [手动点赞], "comments.age": [ 33 ], "comments.rating": [ 7 ]
}, {
"title": [ 狂人日记 ], "body": [ 《狂人日记》是一篇象征性和寓意很强的小说,当时,鲁迅对中国... ]
} }
{
"title": "HUAWEI Mate 50 直屏旗舰 超光变XMAGE影像 北斗卫星消息 低电量应急模式 128GB冰霜银华为鸿蒙手机",
{
"attrInfo.id":[23],
"attrInfo.attrName":["运行内存"],
"attrInfo.attrValue":["8G"]
},
{
"attrInfo.id":[114],
"attrInfo.attrName":["CPU型号"],
"attrInfo.attrValue":["骁龙8 gen 1"]
}
}
每个内部对象都在内部存储为单独的隐藏文档。 这保持了他们的领域之间的关系。
在gmall-service
模块下搭建搜索模块:service-list
<?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>gmall-service</artifactId>
<groupId>com.atguigu.gmall</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-list</artifactId>
<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>
说明:
引入service-product-client模块
引入spring-boot-starter-data-elasticsearch依赖
在父工程中gmall-parent
模块pom.xml中properties节点中指定client版本跟ES服务端版本一致
<elasticsearch.version>7.8.0</elasticsearch.version>
package com.atguigu.gmall;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.cloud.openfeign.EnableFeignClients;
@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@EnableFeignClients
public class ListApp {
public static void main(String[] args) {
SpringApplication.run(ListApp.class, args);
}
}
bootstrap.properties
spring.application.name=service-list
spring.profiles.active=dev
spring.cloud.nacos.discovery.server-addr=192.168.200.128:8848
spring.cloud.nacos.config.server-addr=192.168.200.128: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
说明:Nacos配置文件中添加es配置信息
说明:在gmall-model
模块中已有商品文档实体类,以及平台属性实体类跟ES索引库mapping简历映射
商品文档实体类
package com.atguigu.gmall.list.model;
import lombok.Data;
import org.springframework.data.annotation.Id;
import org.springframework.data.elasticsearch.annotations.DateFormat;
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.Objects;
// Index = goods , Type = info es 7.8.0 逐渐淡化type! 修改!
// es 的分片,副本是为了保证高可用!
@Data
@Document(indexName = "goods" , shards = 3,replicas = 2)
public class Goods {
// 商品Id skuId
@Id
private Long id;
@Field(type = FieldType.Keyword, index = false)
private String defaultImg;
// es 中能分词的字段,这个字段数据类型必须是 text!
@Field(type = FieldType.Text, analyzer = "ik_max_word")
private String title;
@Field(type = FieldType.Double)
private Double price;
// @Field(type = FieldType.Date) 6.8.1
@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;
// 平台属性集合对象
// Nested 支持嵌套查询 允许对象数组彼此独立检索和查询
@Field(type = FieldType.Nested)
private List<SearchAttr> attrs;
}
销售属性实体类
package com.atguigu.gmall.model.list;
import lombok.Data;
import org.springframework.data.elasticsearch.annotations.Field;
import org.springframework.data.elasticsearch.annotations.FieldType;
@Data
public class SearchAttr {
// 平台属性Id
@Field(type = FieldType.Long)
private Long attrId;
// 平台属性值名称
@Field(type = FieldType.Keyword)
private String attrValue;
// 平台属性名
@Field(type = FieldType.Keyword)
private String attrName;
}
package com.atguigu.gmall.list.controller;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.model.Goods;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author: atguigu
* @create: 2023-03-03 10:10
*/
@RestController
@RequestMapping("api/list")
public class ListApiController {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
/**
* 快速创建索引库,设置索引库映射信息(根据实体类上ES相关注解获取)
* @return
*/
@GetMapping("/inner/createIndex")
public Result createIndex(){
//1.创建索引库
elasticsearchRestTemplate.createIndex(Goods.class);
//2.设置索引库石映射信息
elasticsearchRestTemplate.putMapping(Goods.class);
return Result.ok();
}
/**
* 删除索引库
* @return
*/
@GetMapping("/inner/deleteIndex")
public Result deleteIndex() {
elasticsearchRestTemplate.deleteIndex("goods");
return Result.ok();
}
}
在浏览器运行:
http://localhost:8203/api/list/inner/createIndex
通过kibana查看mapping
重点:attrs 数据类型必须是nested !
构建goods数据模型分析
Sku基本信息(详情业务已封装了接口)
Sku分类信息(详情业务已封装了接口)
Sku的品牌信息(无)
Sku对应的平台属性(详情业务已封装了接口)
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/643
service-product
商品微服务模块中增加查询品牌RestFul接口实现
控制器ProductApiController
@Autowired
private BaseTrademarkService baseTrademarkService;
/**
* 根据品牌ID查询品牌信息
*
* @param id
* @return
*/
@ApiOperation("根据品牌ID查询品牌信息")
@GetMapping("/inner/getTrademark/{tmId}")
public BaseTrademark getTrademark(@PathVariable("tmId") Long id) {
return baseTrademarkService.getById(id);
}
在service-product-client
中ProductFeignClient提供Feign API接口
/**
* 根据品牌ID查询品牌信息
*
* @param id
* @return
*/
@ApiOperation("根据品牌ID查询品牌信息")
@GetMapping("/inner/getTrademark/{tmId}")
public BaseTrademark getTrademark(@PathVariable("tmId") Long id);
服务降级类
@Override
public BaseTrademark getTrademark(Long id) {
log.error("[商品服务],getTrademark业务远程调用失败,执行了服务降级");
return null;
}
YAPI接口文档:
service-list
模块ListApiController 处理上下架请求
package com.atguigu.gmall.list.controller;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.model.Goods;
import com.atguigu.gmall.list.service.SearchService;
import io.swagger.annotations.ApiOperation;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.elasticsearch.core.ElasticsearchRestTemplate;
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.RestController;
/**
* @author: atguigu
* @create: 2023-09-08 15:59
*/
@RestController
@RequestMapping("api/list")
public class SearchController {
@Autowired
private ElasticsearchRestTemplate elasticsearchRestTemplate;
@Autowired
private SearchService searchService;
/**
* 创建索引库,本着实用主义快速创建索引库
*
* @return
*/
@GetMapping("/createIndex")
public Result createIndex() {
//1.创建索引库
elasticsearchRestTemplate.createIndex(Goods.class);
//2.设置索引库中字段映射
elasticsearchRestTemplate.putMapping(Goods.class);
return Result.ok();
}
/**
* 仅用于测试-将指定sku商品新增到索引库中
*
* @param skuId
* @return
*/
@ApiOperation("仅用于测试-将指定sku商品新增到索引库中")
@GetMapping("/inner/upperGoods/{skuId}")
public Result upperGoods(@PathVariable("skuId") Long skuId) {
searchService.upperGoods(skuId);
return Result.ok();
}
/**
* 仅用于测试-将指定sku商品新增到索引库中
*
* @param skuId
* @return
*/
@ApiOperation("仅用于测试-将指定sku商品进行删除")
@GetMapping("/inner/lowerGoods/{skuId}")
public Result lowerGoods(@PathVariable("skuId") Long skuId) {
searchService.lowerGoods(skuId);
return Result.ok();
}
}
SearchService
package com.atguigu.gmall.list.service;
public interface SearchService {
/**
* 商品上架,需要构建索引库文档对象;将文档存入索引库
* @param skuId
*/
void upperGoods(Long skuId);
/**
* 商品下架,将商品文档从索引库删除
* @param skuId
*/
void lowerGoods(Long skuId);
}
SearchServiceImpl
package com.atguigu.gmall.list.service.impl;
import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.list.model.Goods;
import com.atguigu.gmall.list.model.SearchAttr;
import com.atguigu.gmall.list.service.SearchService;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.product.model.BaseAttrInfo;
import com.atguigu.gmall.product.model.BaseCategoryView;
import com.atguigu.gmall.product.model.BaseTrademark;
import com.atguigu.gmall.product.model.SkuInfo;
import lombok.extern.slf4j.Slf4j;
import org.elasticsearch.action.delete.DeleteRequest;
import org.elasticsearch.action.index.IndexRequest;
import org.elasticsearch.action.update.UpdateRequest;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.common.xcontent.XContentType;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.io.IOException;
import java.math.BigDecimal;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;
/**
* @author: atguigu
* @create: 2023-09-09 09:12
*/
@Slf4j
@Service
public class SearchServiceImpl implements SearchService {
private static final String INDEX_NAME = "goods";
@Autowired
private ProductFeignClient productFeignClient;
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
@Autowired
private RestHighLevelClient restHighLevelClient;
@Autowired
private RedisTemplate redisTemplate;
/**
* 商品上架,需要构建索引库文档对象;将文档存入索引库
*
* @param skuId
*/
@Override
public void upperGoods(Long skuId) {
try {
//1.构建索引库文档对象Goods-远程调用商品服务多个接口,采用异步+线程池进行并行处理
Goods goods = new Goods();
//1.1 封装商品基本信息
CompletableFuture<SkuInfo> skuInfoCompletableFuture = CompletableFuture.supplyAsync(() -> {
SkuInfo skuInfo = productFeignClient.getSkuInfo(skuId);
if (skuInfo == null) {
throw new RuntimeException("商品不存在!");
}
goods.setId(skuId);
goods.setDefaultImg(skuInfo.getSkuDefaultImg());
goods.setTitle(skuInfo.getSkuName());
goods.setCreatedDate(skuInfo.getCreateTime());
goods.setCreateTime(skuInfo.getCreateTime());
return skuInfo;
}, threadPoolExecutor);
//1.2 封装商品价格
CompletableFuture<Void> priceCompletableFuture = CompletableFuture.runAsync(() -> {
BigDecimal skuPrice = productFeignClient.getSkuPrice(skuId);
goods.setPrice(skuPrice.doubleValue());
}, threadPoolExecutor);
//1.3 封装商品品牌信息
CompletableFuture<Void> trademarkCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
BaseTrademark trademark = productFeignClient.getTrademark(skuInfo.getTmId());
if (trademark != null) {
goods.setTmId(trademark.getId());
goods.setTmName(trademark.getTmName());
goods.setTmLogoUrl(trademark.getLogoUrl());
}
}, threadPoolExecutor);
//1.4 封装商品分类信息
CompletableFuture<Void> categoryCompletableFuture = skuInfoCompletableFuture.thenAcceptAsync(skuInfo -> {
BaseCategoryView categoryView = productFeignClient.getCategoryView(skuInfo.getCategory3Id());
if (categoryView != null) {
goods.setCategory1Id(categoryView.getCategory1Id());
goods.setCategory1Name(categoryView.getCategory1Name());
goods.setCategory2Id(categoryView.getCategory2Id());
goods.setCategory2Name(categoryView.getCategory2Name());
goods.setCategory3Id(categoryView.getCategory3Id());
goods.setCategory3Name(categoryView.getCategory3Name());
}
}, threadPoolExecutor);
//1.5 封装商品平台属性列表
CompletableFuture<Void> attrListCompletableFuture = CompletableFuture.runAsync(() -> {
List<BaseAttrInfo> attrInfoList = productFeignClient.getAttrListBySkuId(skuId);
if (!CollectionUtils.isEmpty(attrInfoList)) {
//将集合泛型从BaseAttrInfo转为SearchAttr
List<SearchAttr> searchAttrList = attrInfoList.stream().map(baseAttrInfo -> {
SearchAttr searchAttr = new SearchAttr();
searchAttr.setAttrId(baseAttrInfo.getId());
searchAttr.setAttrName(baseAttrInfo.getAttrName());
searchAttr.setAttrValue(baseAttrInfo.getAttrValue());
return searchAttr;
}).collect(Collectors.toList());
goods.setAttrs(searchAttrList);
}
}, threadPoolExecutor);
CompletableFuture.allOf(
skuInfoCompletableFuture,
priceCompletableFuture,
categoryCompletableFuture,
trademarkCompletableFuture,
attrListCompletableFuture
).join();
//2.调用ES提供JavaClient将文档存入索引库
//2.1 构建新增文档请求对象
IndexRequest indexRequest = new IndexRequest(INDEX_NAME);
//2.1 设置文档ID
indexRequest.id(skuId.toString());
//2.3 设置文档请求体参数 JSON
String goodJsonStr = JSON.toJSONString(goods);
indexRequest.source(goodJsonStr, XContentType.JSON);
//3.执行新增文档请求
restHighLevelClient.index(indexRequest, RequestOptions.DEFAULT);
} catch (Exception e) {
throw new RuntimeException("文档新增失败:{}", e);
}
}
/**
* 商品下架,将商品文档从索引库删除
*
* @param skuId
*/
@Override
public void lowerGoods(Long skuId) {
try {
//1.构建删除请求对象
DeleteRequest deleteRequest = new DeleteRequest(
INDEX_NAME,
skuId.toString()
);
//2.执行删除文档
restHighLevelClient.delete(deleteRequest, RequestOptions.DEFAULT);
} catch (IOException e) {
log.error("[搜索服务]删除文档失败:文档ID,{},{}\", skuId.toString(), e.getMessage()");
throw new RuntimeException("删除文档失败");
}
}
}
添加数据
通过kibana查看数据
说明:后期学习了MQ,我们可以根据后台系统添加和修改等操作,发送mq消息自动上下架商品
http://localhost:8203/api/list/inner/upperGoods/21
http://localhost:8203/api/list/inner/lowerGoods/21
搜索商品时,后面我们会根据热点排序,何时更新热点?我们在获取商品详情时调用更新
YAPI接口地址:http://192.168.200.128:3000/project/11/interface/api/739
ListApiController
/**
* 对指定商品热门分值进行增加/减少
*
* @param skuId
* @param incrscore
*/
@ApiOperation("对指定商品热门分值进行增加/减少")
@GetMapping("/inner/incrHotScore/{skuId}/{incrscore}")
public void incrHotScore(@PathVariable("skuId") String skuId, @PathVariable("incrscore") int incrscore) {
searchService.incrHotScore(skuId, incrscore);
}
SearchService
/**
* 对指定商品分值(热门商品)进行更新
* 分值
*
* @param skuId
* @param score
* @return
*/
void incrHotScore(Long skuId, int score);
SearchServiceImpl
@Autowired
private RedisTemplate redisTemplate;
/**
* 对指定商品热门分值进行增加/减少
*
* @param skuId
* @param incrscore
*/
@Override
public void incrHotScore(String skuId, int incrscore) {
try {
//1.先修改Redis中商品热门分值
String hotKey = "hot:goods:score";
Double goodsScore = redisTemplate.opsForZSet().incrementScore(hotKey, skuId, incrscore);
//2.再满足写ES条件后再对ES进行更新-稀释写操作
if (goodsScore % 10 == 0) {
UpdateRequest updateRequest = new UpdateRequest(
INDEX_NAME,
skuId);
Goods goods = new Goods();
goods.setHotScore(goodsScore.longValue());
updateRequest.doc(JSON.toJSONString(goods), XContentType.JSON);
restHighLevelClient.update(
updateRequest, RequestOptions.DEFAULT);
}
} catch (IOException e) {
log.error("[搜索服务]更新热门分值异常:商品ID{},异常信息:{}", skuId, e);
throw new RuntimeException("更新热门分值异常");
}
}
在gmall-client
模块下搭建:service-list-client模块。搭建方式如service-item-client
<?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>gmall-client</artifactId>
<groupId>com.atguigu.gmall</groupId>
<version>1.0</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>service-list-client</artifactId>
</project>
提供远程调用的Feign接口
package com.atguigu.gmall.list.client;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.client.impl.ListDegradeFeignClient;
import io.swagger.annotations.ApiOperation;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
@FeignClient(value = "service-list", path = "/api/list", fallback = ListDegradeFeignClient.class)
public interface ListFeignClient {
/**
* 对指定商品分值(热门商品)进行更新
* 分值
*
* @param skuId
* @param score
* @return
*/
@ApiOperation("对指定商品分值(热门商品)进行更新")
@GetMapping("/inner/incrHotScore/{skuId}/{score}")
public Result incrHotScore(@PathVariable("skuId") Long skuId, @PathVariable("score") int score);
}
服务降级类
package com.atguigu.gmall.list.client.impl;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.list.client.ListFeignClient;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* @author: atguigu
* @create: 2023-08-04 10:46
*/
@Slf4j
@Component
public class ListDegradeFeignClient implements ListFeignClient {
@Override
public Result incrHotScore(Long skuId, int score) {
log.error("[商品服务]-提供方接口incrHotScore调用异常");
return null;
}
}
在service-item
模块pom.xml中引入依赖
<dependency>
<groupId>com.atguigu.gmall</groupId>
<artifactId>service-list-client</artifactId>
<version>1.0</version>
</dependency>
接口调用,更新service-item
模块中ItemServiceImpl
汇总商品信息方法:getBySkuId
package com.atguigu.gmall.item.service.impl;
import com.atguigu.gmall.common.constant.RedisConst;
import com.atguigu.gmall.item.service.ItemService;
import com.atguigu.gmall.list.client.ListFeignClient;
import com.atguigu.gmall.product.client.ProductFeignClient;
import com.atguigu.gmall.product.model.*;
import org.apache.commons.lang.StringUtils;
import org.redisson.api.RBloomFilter;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.math.BigDecimal;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ThreadPoolExecutor;
/**
* @author: atguigu
* @create: 2023-07-28 16:31
*/
@Service
@SuppressWarnings("all") //去除警告抑制
public class ItemServiceImpl implements ItemService {
@Autowired
private ProductFeignClient productFeignClient;
@Autowired
private RedissonClient redissonClient;
@Autowired
private ThreadPoolExecutor threadPoolExecutor;
@Autowired
private ListFeignClient listFeignClient;
/**
* 远程调用商品服务,汇总渲染详情页面所需要数据模型
* 1. ${skuInfo} 商品SKU基本信息 包含:名称,默认图片,重量,商品SKU图片列表
* 2. ${categoryView} 商品SKU所属分类 属性:category1Id|name,category2Id|name,category3Id|name
* 3. ${price} 商品价格-实时
* 4. ${spuPosterList} 商品海报图片列表
* 5. ${skuAttrList} 商品SKU所有平台属性以及值
* 6. ${spuSaleAttrList} 商品Spu所有销售属性,以及销售属性值,带选中效果 根据skuId查销售属性
* 7. ${valuesSkuJson} 选中一组销售属性实现完成切换SKU详情页面JSON
*
* @param skuId
* @return
*/
@Override
public Map<String, Object> getItemInfo(Long skuId) {
Map<String, Object> mapResult = new HashMap<>();
//1. ${skuInfo} 商品SKU基本信息 包含:名称,默认图片,重量,商品SKU图片列表
//2. ${categoryView} 商品SKU所属分类 属性:category1Id|name,category2Id|name,category3Id|name
//3. ${price} 商品价格-实时
//4. ${spuPosterList} 商品海报图片列表
//5. ${skuAttrList} 商品SKU所有平台属性以及值
//6. ${spuSaleAttrList} 商品Spu所有销售属性,以及销售属性值,带选中效果 根据skuId查销售属性
//7. ${valuesSkuJson} 选中一组销售属性实现完成切换SKU详情页面JSON
//8.调用搜索服务,更新商品分值
CompletableFuture.runAsync(() -> {
listFeignClient.incrHotScore(skuId, 1);
}).join();
//9.将以上所有异步任务进行组合 必须所有异步任务都执行完毕
CompletableFuture.allOf(
skuInfoCompletableFuture,
priceCompletableFuture,
skuAttrListCompletableFuture,
categoryViewCompletableFuture,
spuPosterListCompletableFuture,
spuSaleAttrListCompletableFuture,
valuesSkuJsonCompletableFuture
).join();
return mapResult;
}
}