谷粒商城笔记合集
二、商品服务 & 商品上架
2.1 存储模型分析
上架的商品才可以在网站展示
上架的商品需要可以检索
Elasticsearch:用于检索数据
logstach:存储数据
Kiban:视图化查看数据
2.2 商品Mapping
分析:商品上架在 es 中是存入 sku 还是 spu ?
检索的时候输入名字,是需要按照 sku 的 title进行全文检索的检索使用商品规格,规格是 spu 的公共属性,每个 spu 是一样的按照分类 id 进去的 都是直接列出 spu的,还可以切换我们如果将 sku 的 全量信息 保存在 es 中 (包括 spu 属性),就太多量字段了如果我们将 spu 以及他包含的 sku 信息保存到 es 中,也可以方
PUT product{"mappings":{"properties":{"skuId":{"type":"long"},"spuId":{"type":"keyword"},"skuTitle":{"type":"text","analyzer": "ik_smart"},"skuPrice":{"type":"keyword"},"skuImg":{"type":"text","analyzer": "ik_smart"},"saleCount":{"type":"long"},"hasStock":{"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},"catalogName":{"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"}}}}}}
2.3 API:商品上架⚠️💡
2.3.1 业务流程
上架前
上架后
业务流程
前端点击上架后,发送请求并带上参数 spuid
根据spuid
查询pms_sku_info
表得到商品相关属性
根据spuid
查询pms_product_attr_value
表得到可以用来检索的规格属性
从ProductAttrValueEntity
中拿到所有的 attrId,根据 attrId 查询pms_attr
查询检索的属性
x根据pms_attr
查询到检索属性后,用检索属性和 原先根据spuid
查询pms_sku_info
表得到商品相关属性进行比较,pms_sku_info
包含 从pms_attr
字段attr_id 则数据保存否则过滤
根据skuIds
去查询远程仓库中是否有库存 SELECT SUM(stock-stock_locked) FROMwms_ware_sku
WHERE sku_id = 1
组装数据 设置 SkuEsModel
发送给 es 保存
2.3.1 公共服务⚠️
创建用于传输sku库存信息的TO,用于商品上架过程中远程获取库存信息:cn/lzwei/common/to/SkuHasStockTo.java
@Datapublic class SkuHasStockTo {private Long skuId;private Boolean hasStock;}
修改公共返回类R,用于商品上架过程中远程获取库存信息:cn/lzwei/common/utils/R.java
public class R<T> extends HashMap<String, Object> {public <T> T getData(TypeReference<T> typeReference) {Object data = get("data");String jsonString = JSON.toJSONString(data);T t = JSON.parseObject(jsonString, typeReference);return t;}public R setData(Object data) {put("data",data);return this;}}
创建用于传输sku上架信息的TO,用于商品上架过程中远程保存sku信息到elasticsearch:cn/lzwei/common/to/search/SpuUpTo.java
@Datapublic class SpuUpTo {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<Attr> attrs;@Datapublic static class Attr{private Long attrId;private String attrName;private String attrValue;}}
新增商品上架过程中批量请求elasticsearch失败、商品上架异常的状态枚举对象:cn/lzwei/common/exception/BizCodeEnume.java
public enum BizCodeEnume {PRODUCT_UP_EXCEPTION(11000,"商品上架异常");}
新增商品状态的枚举常量类,用于商品上架成功后的状态更新:mon.constant.ProductConstant.StatusEnum
public class ProductConstant {public enum StatusEnum{NEW_SPU(0,"新建"),SPU_UP(1,"商品上架"),SPU_DOWN(2,"商品下架");private Integer code;private String msg;StatusEnum(Integer code,String msg){this.code=code;this.msg=msg;}public Integer getCode() {return code;}public String getMsg() {return msg;}}}
2.3.2 仓储服务⚠️
创建封装sku库存状态的VO,封装商品上架远程调用通过skuId列表获取sku对应的库存状态:cn/lzwei/bilimall/ware/vo/SkuHasStockVo.java
@Datapublic class SkuHasStockVo {private Long skuId;private Boolean hasStock;}
WareSkuController:商品上架远程调用:通过skuId列表获取sku对应的库存信息
@RestController@RequestMapping("ware/waresku")public class WareSkuController {@Autowiredprivate WareSkuService wareSkuService;/*** 商品上架远程调用:通过skuId列表获取sku对应的库存信息*/@PostMapping("/getSkusWareInfoBySkuIds")public R getSkusWareInfoBySkuIds(@RequestBody List<Long> skuIds){List<SkuHasStockVo> wareSkuEntities=wareSkuService.getSkusWareInfoBySkuIds(skuIds);return R.ok().setData(wareSkuEntities);}}
WareSkuService:商品上架远程调用:通过skuId列表获取sku对应的库存信息
public interface WareSkuService extends IService<WareSkuEntity> {/*** 商品上架远程调用:通过skuId列表获取sku对应的库存信息*/List<SkuHasStockVo> getSkusWareInfoBySkuIds(List<Long> skuIds);}
WareSkuServiceImpl:商品上架远程调用:通过skuId列表获取sku对应的库存信息
@Service("wareSkuService")public class WareSkuServiceImpl extends ServiceImpl<WareSkuDao, WareSkuEntity> implements WareSkuService {/*** 商品上架远程调用:通过skuId列表获取sku对应的库存信息*/@Overridepublic List<SkuHasStockVo> getSkusWareInfoBySkuIds(List<Long> skuIds) {List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> {SkuHasStockVo skuHasStockVo = new SkuHasStockVo();//1.设置skuIdskuHasStockVo.setSkuId(skuId);//2.通过skuId获取剩余库存:库存量-锁定库存量Long count = baseMapper.getStock(skuId);skuHasStockVo.setHasStock(count==null?false:count > 0);return skuHasStockVo;}).collect(Collectors.toList());return collect;}}
WareSkuDao.java:获取sku剩余库存:库存量-锁定库存量
@Mapperpublic interface WareSkuDao extends BaseMapper<WareSkuEntity> {//通过skuId获取剩余库存:库存量-锁定库存量Long getStock(Long skuId);}
WareSkuDao.xml:获取sku剩余库存:库存量-锁定库存量
<mapper namespace="cn.lzwei.bilimall.ware.dao.WareSkuDao"><!--通过skuId获取剩余库存:库存量-锁定库存量--><select id="getStock" resultType="java.lang.Long">SELECT SUM(stock-stock_locked) FROM `wms_ware_sku` WHERE sku_id=#{skuId}</select></mapper>
2.3.3 检索服务⚠️
创建常量类,保存商品索引常量:cn/lzwei/bilimall/search/constant/EsConstant.java
public class EsConstant {//商品索引public static final String PRODUCT_INDEX="product";}
ProductSaveController
@RequestMapping("/search/save")@RestController@Slf4jpublic class ProductSaveController {@ResourceProductSaveService productSaveService;/*** 保存商品信息*/@PostMapping("/product")public R productStatusUp(@RequestBody List<SpuUpTo> spuUpTo){boolean b=false;try {b = productSaveService.productStatusUp(spuUpTo);} catch (IOException e) {log.error("ProductSaveController商品上架错误:{}",e);return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnume.PRODUCT_UP_EXCEPTION.getMessage());}if (!b){return R.ok();}else {return R.error(BizCodeEnume.PRODUCT_UP_EXCEPTION.getCode(),BizCodeEnume.PRODUCT_UP_EXCEPTION.getMessage());}}}
ProductSaveService
public interface ProductSaveService {/*** 保存商品信息*/boolean productStatusUp(List<SpuUpTo> spuUpTo) throws IOException;}
ProductSaveServiceImpl
@Service("productSaveService")@Slf4jpublic class ProductSaveServiceImpl implements ProductSaveService {@ResourceRestHighLevelClient restHighLevelClient;/*** 保存商品信息*/@Overridepublic boolean productStatusUp(List<SpuUpTo> spuUpTo) throws IOException {//1.构造批量请求BulkRequest bulkRequest = new BulkRequest();for (SpuUpTo upTo : spuUpTo) {//1.1、构造请求 指定es索引IndexRequest indexRequest = new IndexRequest(EsConstant.PRODUCT_INDEX);//1.2、设置idindexRequest.id(String.valueOf(upTo.getSkuId()));//1.3、设置数据String s = JSON.toJSONString(upTo);indexRequest.source(s, XContentType.JSON);//1.4、添加请求bulkRequest.add(indexRequest);}//2.执行批量请求BulkResponse bulk = restHighLevelClient.bulk(bulkRequest, MON_OPTIONS);boolean b = bulk.hasFailures();//获取上架的skuIdList<String> collect = Arrays.stream(bulk.getItems()).map(item -> {return item.getId();}).collect(Collectors.toList());//3.如果批量错误//TODO 商品上架错误if (b){log.error("商品上架错误:{}",collect);}else {log.info("商品上架成功的打印:{}",collect);}return b;}}
2.3.4 商品服务💡
SpuInfoController:将所属的sku信息保存到elasticsearch中
@RestController@RequestMapping("product/spuinfo")public class SpuInfoController {@Autowiredprivate SpuInfoService spuInfoService;/*** 商品上架:将所属的sku信息保存到elasticsearch中*/@PostMapping("/{spuId}/up")public R up(@PathVariable(value = "spuId") Long spuId){R r=spuInfoService.up(spuId);return r;}}
SpuInfoService:将所属的sku信息保存到elasticsearch中
public interface SpuInfoService extends IService<SpuInfoEntity> {/*** 商品上架:将所属的sku信息保存到elasticsearch中*/R up(Long spuId);}
SpuInfoServiceImpl:将所属的sku信息保存到elasticsearch中
@Service("spuInfoService")public class SpuInfoServiceImpl extends ServiceImpl<SpuInfoDao, SpuInfoEntity> implements SpuInfoService {@ResourceProductAttrValueService productAttrValueService;@ResourceAttrService attrService;@ResourceSkuInfoService skuInfoService;@ResourceBrandService brandService;@ResourceCategoryService categoryService;@ResourceWareFeignService wareFeignService;@ResourceSearchFeignService searchFeignService;/*** 商品上架:将所属的sku信息保存到elasticsearch中*/@Transactional@Overridepublic R up(Long spuId) {//1.准备spu的品牌信息、分类信息:通过spuId获取品牌信息、分类信息SpuInfoEntity spuInfo = this.getById(spuId);Long brandId = spuInfo.getBrandId();Long catalogId = spuInfo.getCatalogId();BrandEntity brandInfo = brandService.getById(brandId);CategoryEntity categoryInfo = categoryService.getById(catalogId);//2.准备spu的所有可检索的规格参数//获取商品的所有规格参数List<ProductAttrValueEntity> productAttrValueEntities = productAttrValueService.list(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));List<Long> productAttrIds = productAttrValueEntities.stream().map(item -> item.getAttrId()).collect(Collectors.toList());//获取可用于检索的规格参数List<Long> searchAttrIds = attrService.getSearchAttr(productAttrIds);HashSet<Long> searchAttrIdsSet=new HashSet<>(searchAttrIds);//过滤出商品的所有可用于检索的规格参数List<SpuUpTo.Attr> searchAttrs = productAttrValueEntities.stream().filter(item -> {return searchAttrIdsSet.contains(item.getAttrId());}).map(item -> {SpuUpTo.Attr attr = new SpuUpTo.Attr();BeanUtils.copyProperties(item, attr);return attr;}).collect(Collectors.toList());//3.查出所有的skuList<SkuInfoEntity> skuInfoEntities = skuInfoService.list(new QueryWrapper<SkuInfoEntity>().eq("spu_id", spuId));//4.准备所有sku的库存信息:传递skuId列表获取对应的库存信息,使用map封装。(远程调用库存服务)List<Long> skuIds = skuInfoEntities.stream().map(item -> {return item.getSkuId();}).collect(Collectors.toList());Map<Long, Boolean> stockMap=null;try{R r = wareFeignService.getSkusWareInfoBySkuIds(skuIds);TypeReference<List<SkuHasStockTo>> typeReference = new TypeReference<List<SkuHasStockTo>>(){};List<SkuHasStockTo> stocks = r.getData(typeReference);stockMap = stocks.stream().collect(Collectors.toMap(SkuHasStockTo::getSkuId, SkuHasStockTo::getHasStock));}catch (Exception e){log.error("远程调用库存服务失败:{}",e);System.out.println(e.getCause());}//5.遍历所有sku:进行数据封装,封装成TOMap<Long, Boolean> finalStockMap = stockMap;List<SpuUpTo> spuUpTos = skuInfoEntities.stream().map(item -> {SpuUpTo spuUpTo = new SpuUpTo();//5.1、属性对拷BeanUtils.copyProperties(item,spuUpTo);//TODO 商品上架:热点评分设置//5.2、设置价格、图片、热点评分spuUpTo.setSkuPrice(item.getPrice());spuUpTo.setSkuImg(item.getSkuDefaultImg());spuUpTo.setHotScore(0l);//5.3、设置品牌名称、品牌图片spuUpTo.setBrandName(brandInfo.getName());spuUpTo.setBrandImg(brandInfo.getLogo());//5.4、设置分类名spuUpTo.setCatalogName(categoryInfo.getName());spuUpTo.setCatalogId(catalogId);//5.5、设置库存if(finalStockMap ==null){spuUpTo.setHasStock(true);}else {spuUpTo.setHasStock(finalStockMap.get(item.getSkuId()));}//5.6、设置规格参数:attrId、attrName、attrValuespuUpTo.setAttrs(searchAttrs);return spuUpTo;}).collect(Collectors.toList());//5.将封装好的TO传递给 bilimall-search服务,保存到elasticsearch中(远程调用)R r = searchFeignService.productStatusUp(spuUpTos);if(r.getCode()==0){//远程调用成功:修改商品状态为上架baseMapper.updateSpuStatus(spuId, ProductConstant.StatusEnum.SPU_UP.getCode());}else {//远程调用失败//TODO 商品上架:重复调用?接口幂等性?重试机制?}return r;}}
AttrService:获取可用于检索的规格参数
public interface AttrService extends IService<AttrEntity> {/*** 获取可用于检索的规格参数*/List<Long> getSearchAttr(List<Long> productAttrIds);}
AttrServiceImpl:获取可用于检索的规格参数
@Service("attrService")public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {/*** 获取可用于检索的规格参数*/@Overridepublic List<Long> getSearchAttr(List<Long> productAttrIds) {List<Long> searchIds=baseMapper.getSearchAttr(productAttrIds);return searchIds;}}
AttrDao.java:获取可用于检索的规格参数
@Mapperpublic interface AttrDao extends BaseMapper<AttrEntity> {/*** 获取可用于检索的规格参数*/List<Long> getSearchAttr(List<Long> productAttrIds);}
AttrDao.xml:获取可用于检索的规格参数
<mapper namespace="cn.lzwei.bilimall.product.dao.AttrDao"><!-- 获取可用于检索的规格参数 --><select id="getSearchAttr" resultType="java.lang.Long">SELECT attr_id FROM `pms_attr` WHERE search_type=1 And attr_id IN<foreach collection="productAttrIds" item="item" separator="," open="(" close=")">#{item}</foreach></select></mapper>
创建库存服务的远程调用接口:cn/lzwei/bilimall/product/feign/WareFeignService.java
@FeignClient(name = "bilimall-ware")public interface WareFeignService {/*** 商品上架远程调用:通过skuId列表获取sku对应的库存信息*/@PostMapping("/ware/waresku/getSkusWareInfoBySkuIds")R getSkusWareInfoBySkuIds(@RequestBody List<Long> skuIds);}
创建索引服务的远程调用接口:cn/lzwei/bilimall/product/feign/SearchFeignService.java
@FeignClient("bilimall-search")public interface SearchFeignService {/*** 保存商品信息*/@PostMapping("//search/save/product")R productStatusUp(@RequestBody List<SpuUpTo> spuUpTo);}
SpuInfoDao.java:上架成功修改商品状态为上架
@Mapperpublic interface SpuInfoDao extends BaseMapper<SpuInfoEntity> {//远程调用成功:修改商品状态为上架void updateSpuStatus(@Param("spuId") Long spuId, @Param("code") Integer code);}
SpuInfoDao.xml:上架成功修改商品状态为上架
<mapper namespace="cn.lzwei.bilimall.product.dao.SpuInfoDao"><update id="updateSpuStatus">UPDATE `pms_spu_info` SET publish_status=#{code},update_time=NOW() WHERE id=#{spuId}</update></mapper>