谷粒商城笔记合集
分布式基础篇 | 分布式高级篇 | 高可用集群篇 |
---|---|---|
===简介&环境搭建=== | ===Elasticsearch=== | |
项目简介与分布式概念(第一、二章) | Elasticsearch:全文检索(第一章) | |
基础环境搭建(第三章) | ===商品服务开发=== | |
===整合SpringCloud=== | 商品服务 & 商品上架(第二章) | |
整合SpringCloud、SpringCloud alibaba(第四、五章) | ===商城首页开发=== | |
===前端知识=== | 商城业务:首页整合、Nginx 域名访问、性能优化与压力测试 (第三、四、五章) | |
前端开发基础知识(第六章) | 缓存与分布式锁(第六章) | |
===商品服务开发=== | ===商城检索开发=== | |
商品服务开发:基础概念、三级分类(第七、八章) | 商城业务:商品检索(第七章) | |
商品服务开发:品牌管理(第九章) | ||
商品服务开发:属性分组、平台属性(第十、十一章) | ||
商品服务:商品维护(第十二、十三章) | ||
===仓储服务开发=== | ||
仓储服务:仓库维护(第十四章) | ||
基础篇总结(第十五章) |
二、商品服务 & 商品上架
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
@Data public 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
@Data public 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; @Data public 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,"商品上架异常"); }
-
新增商品状态的枚举常量类,用于商品上架成功后的状态更新:cn.lzwei.common.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
@Data public class SkuHasStockVo { private Long skuId; private Boolean hasStock; }
-
WareSkuController:商品上架远程调用:通过skuId列表获取sku对应的库存信息
@RestController @RequestMapping("ware/waresku") public class WareSkuController { @Autowired private 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对应的库存信息 */ @Override public List<SkuHasStockVo> getSkusWareInfoBySkuIds(List<Long> skuIds) { List<SkuHasStockVo> collect = skuIds.stream().map(skuId -> { SkuHasStockVo skuHasStockVo = new SkuHasStockVo(); //1.设置skuId skuHasStockVo.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剩余库存:库存量-锁定库存量
@Mapper public 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 @Slf4j public class ProductSaveController { @Resource ProductSaveService 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") @Slf4j public class ProductSaveServiceImpl implements ProductSaveService { @Resource RestHighLevelClient restHighLevelClient; /** * 保存商品信息 */ @Override public 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、设置id indexRequest.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, BilimallElasticsearchConfig.COMMON_OPTIONS); boolean b = bulk.hasFailures(); //获取上架的skuId List<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 { @Autowired private 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 { @Resource ProductAttrValueService productAttrValueService; @Resource AttrService attrService; @Resource SkuInfoService skuInfoService; @Resource BrandService brandService; @Resource CategoryService categoryService; @Resource WareFeignService wareFeignService; @Resource SearchFeignService searchFeignService; /** * 商品上架:将所属的sku信息保存到elasticsearch中 */ @Transactional @Override public 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.查出所有的sku List<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:进行数据封装,封装成TO Map<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、attrValue spuUpTo.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 { /** * 获取可用于检索的规格参数 */ @Override public List<Long> getSearchAttr(List<Long> productAttrIds) { List<Long> searchIds=baseMapper.getSearchAttr(productAttrIds); return searchIds; } }
-
AttrDao.java:获取可用于检索的规格参数
@Mapper public 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:上架成功修改商品状态为上架
@Mapper public 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>