1.上架功能
1.1 业务逻辑
商品数据—》保存到ElasticSearch—》可以被检索到
1.2 商品数据保存到ElasticSearch的理由
- 内存运算
- 检索性能强大
- 可以分片存放数据
1.3 常见的保存到ElasticSearch的数据格式
格式一:
{
skuId:1
spuId:1
skuTitle:小米
price:1000
saleCount:999
attrs[
{尺寸:1寸},
{CPU:麒麟996},
{分辨率:高清}
]
}
产生的问题:冗余字段(空间换时间)
格式二:
{
skuId:1
spuId:1
}
attr索引{
spuId:1,
attrs[
{尺寸:1寸},
{CPU:麒麟996},
{分辨率:高清}
]
}
产生的问题:并发高的情况下,传输的数据过大(时间换空间)
1.4 本项目所采用的格式
{
"mappings":{
"properties":{
"skuId":{
"type":"long"
},
"spuId":{
"type":"keyword"
},
"skuTitle":{
"type":"text",
"analyzer":"ik_smart" //使用ik分词器进行检索
},
"skuPrice":{
"type":"keyword"
},
"skuImg":{
"type":"keyword",
"index":false, //默认为true,false表示不参与检索
"doc_values":false //默认为true,false表示不参与聚合
},
"skuCount":{
"type":"long"
},
"hasStock":{ //是否有库存
"type":"boolean"
},
"hotScore":{ //热度计算
"type":"long"
},
"brandId":{ //品牌id
"type":"long"
},
"categoryId":{ //分类id
"type":"long"
},
"brandName":{ //品牌名字
"type":"keyword",
"index":false, //默认为true,false表示不参与检索
"doc_values":false //默认为true,false表示不参与聚合
},
"brandImg":{ //品牌图片
"type":"keyword",
"index":false, //默认为true,false表示不参与检索
"doc_values":false //默认为true,false表示不参与聚合
},
"categoryName":{ //分类名字
"type":"keyword",
"index":false, //默认为true,false表示不参与检索
"doc_values":false //默认为true,false表示不参与聚合
},
"attrs":{ //属性集合
"type":"nested", //嵌入式,表示该字段内部属性参与检索和聚合
"properties":{
"attrId":{
"type":"long"
},
"attrName":{
"type":"keyword",
"index":false, //默认为true,false表示不参与检索
"doc_values":false //默认为true,false表示不参与聚合
},
"attrValue":{
"type":"keyword"
}
}
}
}
}
}
1.5 代码逻辑
- 查出当前spuId对应的所有sku信息,品牌的名字
- 封装每个sku的信息
- 发送远程调用,向库存系统查询是否有库存
- 热度评分计算
- 查询品牌和分类的名字信息
- 查询当前sku所有可以被用来检索的规格属性
- 将数据发送给es进行保存
public void up(Long spuId) {
//1.查出当前spuId对应的所有sku信息,品牌的名字
List<SkuInfoEntity> skus = skuInfoService.getSkusBySpuId(spuId);
List<Long> skuIdList = skus.stream().map(
SkuInfoEntity::getSkuId
).collect(Collectors.toList());
//2.4 查询当前sku所有可以被用来检索的规格属性
//baseAttrs:当前spu下的所有属性的集合
//searchAttrIds:可以被检索到的属性
//处理逻辑:在baseAttrs筛选出可以被检索的属性
List<ProductAttrValueEntity> baseAttrs = attrValueService.baseAttrlistforspu(spuId);
List<Long> attrIds = baseAttrs.stream().map((attr) -> {
return attr.getAttrId();
}).collect(Collectors.toList());
//在指定的所有属性集合里面,选出可以被检索的属性
List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);
HashSet<Long> idSet = new HashSet<>(searchAttrIds);
List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter((item) -> {
//在baseAttrs筛选出可以被检索的属性,即baseAttrs的元素的id是否在idSet里面
return idSet.contains(item.getAttrId());
}).map((item) -> {
SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
BeanUtils.copyProperties(item, attrs);
return attrs;
}).collect(Collectors.toList());
//2.1 发送远程调用,向库存系统查询是否有库存
Map<Long, Boolean> stockMap = null;
try {
R r = wareFeignService.getSkusHasStock(skuIdList);
TypeReference typeReference = new TypeReference<List<SkuHasStockVo>>() {
};
List<SkuHasStockVo> data = (List<SkuHasStockVo>) r.getData(typeReference);
stockMap = data.stream().collect(Collectors.toMap(SkuHasStockVo::getSkuId, (item) -> {
return item.getHasStock();
}));
} catch (Exception e) {
log.error("库存服务查询异常:原因{}",e);
}
//2.封装每个sku的信息
Map<Long, Boolean> finalStockMap = stockMap;
List<SkuEsModel> upProducts = skus.stream().map((sku) -> {
//组装需要的数据
SkuEsModel esModel = new SkuEsModel();
BeanUtils.copyProperties(sku, esModel);
//处理sku和esModel不同的字段
//sku和esModel名字不同的字段:SkuImg,SkuPrice
esModel.setSkuImg(sku.getSkuDefaultImg());
esModel.setSkuPrice(sku.getPrice());
//sku未有的字段:hasStock,hotScore
//2.1 发送远程调用,向库存系统查询是否有库存
if (finalStockMap == null){
esModel.setHasStock(true);
}else {
esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
}
//TODO 2.2 热度评分计算
esModel.setHotScore(0L);
//2.3 查询品牌和分类的名字信息
BrandEntity brand = brandService.getById(sku.getBrandId());
esModel.setBrandImg(brand.getLogo());
esModel.setBrandName(brand.getName());
CategoryEntity category = categoryService.getById(esModel.getCatalogId());
esModel.setCatalogName(category.getName());
//2.4 查询当前sku所有可以被用来检索的规格属性
esModel.setAttrs(attrsList);
return esModel;
}).collect(Collectors.toList());
//3 将数据发送给es进行保存
R result = searchFeignService.productStatusUp(upProducts);
if (result.getCode() == 0){
//远程调用成功
//修改当前spu的状态,改为已上架
baseMapper.updateStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());
}else {
//远程调用失败
//TODO 产生的问题:重复调用,接口幂等性
}
}
1.6 Feign调用流程
1.构造请求数据,将对象转为JSON
RequestTemplate template = buildTemplateFromArgs.create(argv)
2.发送请求进行执行(执行成功会进行解码响应数据)
executeAndDecode(template)
3.执行请求会有重试机制
while(true){
try{
executeAndDecode(template)
}catch(){
try{retryer.continueOrPropagate(e);}catch(){
}
continue;
}
}