✅P130-135_商城业务-商品上架-完整流程
商品上架接口
url
POST:/product/spuinfo/{spuId}/up
请求参数
无
响应数据
{
"msg": "success",
"code": 0
}
构造基本数据
cfmall-common/src/main/java/com/gyz/common/es/SkuEsModel.java
package com.gyz.common.es;
import lombok.Data;
import java.math.BigDecimal;
import java.util.List;
@Data
public class SkuEsModel {
private Long skuId;
private Long spuId;
private String skuTitle;
private BigDecimal skuPrice;
private String skuImg;
private Long saleCount;
private Boolean hasStock;
private Long hotScore;
private Long brandId;
private Long catalogId;
private String brandName;
private String brandImg;
private String catalogName;
private List<Attrs> attrs;
@Data
public static class Attrs {
private Long attrId;
private String attrName;
private String attrValue;
}
}
编写商品上架逻辑
Controller
cfmall-product/src/main/java/com/gyz/cfmall/product/controller/SpuInfoController.java
@Autowired
private SpuInfoService spuInfoService;
@RequestMapping("/{spuId}/up")
public R spuUp(@PathVariable("spuId") Long spuId) {
spuInfoService.up(spuId);
return R.ok();
}
Service
/**
* 商品上架
* @param spuId :
* @return void
*/
void up(Long spuId);
cfmall-product/src/main/java/com/gyz/cfmall/product/service/impl/SpuInfoServiceImpl.java
/**
* @param spuId :
* @return void
*/
@GlobalTransactional(rollbackFor = Exception.class)
@Override
public void up(Long spuId) {
//1、查出当前spuId对应的所有sku信息,品牌的名字
List<SkuInfoEntity> skuInfoEntities = skuInfoService.getSkusBySpuId(spuId);
// 2、查出当前sku的所有可以被用来检索的规格属性
List<ProductAttrValueEntity> baseAttrs = productAttrValueService.baseAttrListforspu(spuId);
List<Long> attrIds = baseAttrs.stream().map(attr -> {
return attr.getAttrId();
}).collect(Collectors.toList());
//3、查询可检索的属性
List<Long> searchAttrIds = attrService.selectSearchAttrs(attrIds);
//转换为Set集合
Set<Long> idSet = searchAttrIds.stream().collect(Collectors.toSet());
List<SkuEsModel.Attrs> attrsList = baseAttrs.stream().filter(item -> {
return idSet.contains(item.getAttrId());
}).map(item -> {
SkuEsModel.Attrs attrs = new SkuEsModel.Attrs();
BeanUtils.copyProperties(item, attrs);
return attrs;
}).collect(Collectors.toList());
List<Long> skuIdList = skuInfoEntities.stream()
.map(SkuInfoEntity::getSkuId)
.collect(Collectors.toList());
//4、发送远程调用,库存系统查询是否有库存
Map<Long, Boolean> stockMap = null;
try {
R skuHasStock = wareFeignService.getSkuHasStock(skuIdList);
TypeReference<List<SkuHasStockVo>> typeReference = new TypeReference<List<SkuHasStockVo>>() {
};
stockMap = skuHasStock.getData(typeReference).stream()
.collect(Collectors.toMap(SkuHasStockVo::getSkuId, item -> item.getHasStock()));
} catch (Exception e) {
log.error("库存服务查询异常:原因{}", e);
}
//5、封装每个sku的信息
Map<Long, Boolean> finalStockMap = stockMap;
List<SkuEsModel> collect = skuInfoEntities.stream().map(sku -> {
//1、组装需要的数据
SkuEsModel esModel = new SkuEsModel();
esModel.setSkuPrice(sku.getPrice());
esModel.setSkuImg(sku.getSkuDefaultImg());
//设置库存信息
if (finalStockMap == null) {
esModel.setHasStock(true);
} else {
esModel.setHasStock(finalStockMap.get(sku.getSkuId()));
}
// 2、热度评分。0
esModel.setHotScore(0L);
// 3、查询品牌和分类的名字信息
BrandEntity brandEntity = brandService.getById(sku.getBrandId());
esModel.setBrandName(brandEntity.getName());
esModel.setBrandId(brandEntity.getBrandId());
esModel.setBrandImg(brandEntity.getLogo());
CategoryEntity categoryEntity = categoryService.getById(sku.getCatalogId());
esModel.setCatalogId(categoryEntity.getCatId());
esModel.setCatalogName(categoryEntity.getName());
//设置检索属性
esModel.setAttrs(attrsList);
BeanUtils.copyProperties(sku, esModel);
return esModel;
}).collect(Collectors.toList());
// 6、将数据发给es进行保存:cfmall-search
R r = searchFeignService.productStatusUp(collect);
if (r.getCode() == 0) {
//远程调用成功
// 7、修改当前spu的状态
this.baseMapper.updaSpuStatus(spuId, ProductConstant.ProductStatusEnum.SPU_UP.getCode());
} else {
//远程调用失败
//TODO 8、重复调用?接口幂等性:重试机制
}
}
1、查出当前spuId对应的所有sku信息,品牌的名字
cfmall-product/src/main/java/com/gyz/cfmall/product/service/SkuInfoService.java
List<SkuInfoEntity> getSkusBySpuId(Long spuId);
cfmall-product/src/main/java/com/gyz/cfmall/product/service/impl/SkuInfoServiceImpl.java
@Override
public List<SkuInfoEntity> getSkusBySpuId(Long spuId) {
List<SkuInfoEntity> skuInfoEntities = this.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));
return skuInfoEntities;
}
2、查出当前sku的所有可以被用来检索的规格属性
cfmall-product/src/main/java/com/gyz/cfmall/product/service/ProductAttrValueService.java
List<ProductAttrValueEntity> baseAttrListforspu(Long spuId);
cfmall-product/src/main/java/com/gyz/cfmall/product/service/impl/ProductAttrValueServiceImpl.java
@Override
public List<ProductAttrValueEntity> baseAttrListforspu(Long spuId) {
List<ProductAttrValueEntity> attrValueEntityList = this.baseMapper.selectList(
new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
return attrValueEntityList;
}
3、查询可检索的属性
cfmall-product/src/main/java/com/gyz/cfmall/product/service/AttrService.java
List<Long> selectSearchAttrs(List<Long> attrIds);
cfmall-product/src/main/java/com/gyz/cfmall/product/service/impl/AttrServiceImpl.java
@Override
public List<Long> selectSearchAttrs(List<Long> attrIds) {
List<Long> searchAttrIds = this.baseMapper.selectSearchAttrIds(attrIds);
return searchAttrIds;
}
cfmall-product/src/main/java/com/gyz/cfmall/product/dao/AttrDao.java
@Mapper
public interface AttrDao extends BaseMapper<AttrEntity> {
List<Long> selectSearchAttrIds(@Param("attrIds") List<Long> attrIds);
}
cfmall-product/src/main/resources/mapper/product/AttrDao.xml
<select id="selectSearchAttrIds" resultType="java.lang.Long">
SELECT attr_id FROM pms_attr WHERE attr_id IN
<foreach collection="attrIds" item="id" separator="," open="(" close=")">
#{id}
</foreach>
AND search_type = 1;
</select>
4、发送远程调用,库存系统查询是否有库存
cfmall-product/src/main/java/com/gyz/cfmall/product/feign/WareFeignService.java
package com.gyz.cfmall.product.feign;
import com.gyz.common.utils.R;
import org.springframework.cloud.openfeign.FeignClient;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import java.util.List;
@FeignClient("cfmall-ware")
public interface WareFeignService {
@PostMapping(value = "/ware/waresku/hasStock")
R getSkuHasStock(@RequestBody List<Long> skuIds);
}
cfmall-product/src/main/java/com/gyz/cfmall/product/feign/SearchFeignService.java
@FeignClient("cfmall-search")
public interface SearchFeignService {
@PostMapping(value = "/search/save/product")
public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels);
}
远程查询库存
分析:一个商品可以存放在多个仓库中,因此,需要累加,但是还需要减去被锁定的商品即未支付的商品数量
cfmall-ware/src/main/java/com/gyz/cfmall/ware/controller/WareSkuController.java
@Resource
private WareSkuService wareSkuService;
/**
* 查询sku是否有库存
* @return
*/
@PostMapping(value = "/hasStock")
public R getSkuHasStock(@RequestBody List<Long> skuIds) {
//skuId stock
List<SkuHasStockVo> vos = wareSkuService.getSkuHasStock(skuIds);
return R.ok().setData(vos);
}
cfmall-common/src/main/java/com/gyz/common/to/SkuHasStockVo.java
package com.gyz.common.to;
import lombok.Data;
@Data
public class SkuHasStockVo {
private Long skuId;
private Boolean hasStock;
}
cfmall-ware/src/main/java/com/gyz/cfmall/ware/service/WareSkuService.java
/**
* 判断是否有库存
* @param skuIds
* @return
*/
List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds);
cfmall-ware/src/main/java/com/gyz/cfmall/ware/service/impl/WareSkuServiceImpl.java
@Override
public List<SkuHasStockVo> getSkuHasStock(List<Long> skuIds) {
List<SkuHasStockVo> skuHasStockVos = skuIds.stream().map(item -> {
Long count = this.baseMapper.getSkuStock(item);
SkuHasStockVo skuHasStockVo = new SkuHasStockVo();
skuHasStockVo.setSkuId(item);
skuHasStockVo.setHasStock(count == null ? false : count > 0);
return skuHasStockVo;
}).collect(Collectors.toList());
return skuHasStockVos;
}
cfmall-ware/src/main/java/com/gyz/cfmall/ware/dao/WareSkuDao.java
@Mapper
public interface WareSkuDao extends BaseMapper<WareSkuEntity> {
Long getSkuStock(Long item);
}
cfmall-ware/src/main/resources/mapper/ware/WareSkuDao.xml
<select id="getSkuStock" resultType="java.lang.Long">
SELECT SUM(stock - stock_locked) FROM wms_ware_sku WHERE sku_id = #{skuId}
</select>
ES服务保存上架商品
6、将数据发给es进行保存:cfmall-search
cfmall-search/src/main/java/com/gyz/cfmall/search/controller/ElasticSaveController.java
@Autowired
private ProductSaveService productSaveService;
/**
* 上架商品
* @param skuEsModels
* @return
*/
@PostMapping(value = "/product")
public R productStatusUp(@RequestBody List<SkuEsModel> skuEsModels) {
boolean status = false;
try {
status = productSaveService.productStatusUp(skuEsModels);
} catch (IOException e) {
//log.error("商品上架错误{}",e);
return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMessage());
}
if (status) {
return R.error(BizCodeEnum.PRODUCT_UP_EXCEPTION.getCode(), BizCodeEnum.PRODUCT_UP_EXCEPTION.getMessage());
} else {
return R.ok();
}
}
public interface ProductSaveService {
boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException;
}
cfmall-search/src/main/java/com/gyz/cfmall/search/service/impl/ProductSaveServiceImpl.java
@Autowired
private RestHighLevelClient esRestClient;
@Override
public boolean productStatusUp(List<SkuEsModel> skuEsModels) throws IOException {
//1.在es中建立索引,建立号映射关系(doc/json/product-mapping.json)
//2. 在ES中保存这些数据
BulkRequest bulkRequest = new BulkRequest();
for (SkuEsModel skuEsModel : skuEsModels) {
//构造保存请求
IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);
indexRequest.id(skuEsModel.getSkuId().toString());
String jsonString = JSON.toJSONString(skuEsModel);
indexRequest.source(jsonString, XContentType.JSON);
bulkRequest.add(indexRequest);
}
BulkResponse bulk = esRestClient.bulk(bulkRequest, CfMallElasticSearchConfig.COMMON_OPTIONS);
//TODO 如果批量错误
boolean hasFailures = bulk.hasFailures();
List<String> collect = Arrays.asList(bulk.getItems()).stream().map(item -> {
return item.getId();
}).collect(Collectors.toList());
log.info("商品上架完成:{}", collect);
return hasFailures;
}
创建映射
在kibana的Dev Tools中执行
PUT product
{
"mappings": {
"properties": {
"skuId": {
"type": "long"
},
"spuId": {
"type": "long"
},
"skuTitle": {
"type": "text",
"analyzer": "ik_smart"
},
"skuPrice": {
"type": "keyword"
},
"skuImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"saleCount": {
"type": "long"
},
"hosStock": {
"type": "boolean"
},
"hotScore": {
"type": "long"
},
"brandId": {
"type": "long"
},
"catelogId": {
"type": "long"
},
"brandName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"brandImg": {
"type": "keyword",
"index": false,
"doc_values": false
},
"catelogName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrs": {
"type": "nested",
"properties": {
"attrId": {
"type": "long"
},
"attrName": {
"type": "keyword",
"index": false,
"doc_values": false
},
"attrValue": {
"type": "keyword"
}
}
}
}
}
}
IDEA-Compound:一键启动多个服务
配置Compound
配置完成后,点击运行即可,服务会不断地启动完成
测试
未上架之前查询商品信息
上架之后查询索引product
问题
磁盘空间不足问题
在上架时发现商品保存到ES出现错误如下:
{"index":"product","type":"_doc","id":"6","cause":{"type":"exception","reason":"Elasticsearch exception [type=cluster_block_exception, reason=index [product] blocked by: [FORBIDDEN/12**/index read-only** / allow delete (api)];]"},"status":403}
原因为磁盘空间不足,索引为只读状态,不可写入。
解决办法:对磁盘空间进行扩容