【谷粒商城高级篇】商品服务 & 商品上架

在这里插入图片描述

谷粒商城笔记合集

分布式基础篇分布式高级篇高可用集群篇
===简介&环境搭建======Elasticsearch===
项目简介与分布式概念(第一、二章)Elasticsearch:全文检索(第一章)
基础环境搭建(第三章)===商品服务开发===
===整合SpringCloud===商品服务 & 商品上架(第二章)
整合SpringCloud、SpringCloud alibaba(第四、五章)===商城首页开发===
===前端知识===商城业务:首页整合、Nginx 域名访问、性能优化与压力测试 (第三、四、五章)
前端开发基础知识(第六章)缓存与分布式锁(第六章)
===商品服务开发======商城检索开发===
商品服务开发:基础概念、三级分类(第七、八章)商城业务:商品检索(第七章)
商品服务开发:品牌管理(第九章)
商品服务开发:属性分组、平台属性(第十、十一章)
商品服务:商品维护(第十二、十三章)
===仓储服务开发===
仓储服务:仓库维护(第十四章)
基础篇总结(第十五章)

二、商品服务 & 商品上架

2.1 存储模型分析

  • 上架的商品才可以在网站展示

  • 上架的商品需要可以检索

Elasticsearch:用于检索数据

logstach:存储数据

Kiban:视图化查看数据

在这里插入图片描述

2.2 商品Mapping

分析:商品上架在 es 中是存入 sku 还是 spu ?

  1. 检索的时候输入名字,是需要按照 sku 的 title进行全文检索的
  2. 检索使用商品规格,规格是 spu 的公共属性,每个 spu 是一样的
  3. 按照分类 id 进去的 都是直接列出 spu的,还可以切换
  4. 我们如果将 sku 的 全量信息 保存在 es 中 (包括 spu 属性),就太多量字段了
  5. 如果我们将 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) FROM wms_ware_sku WHERE sku_id = 1

  • 组装数据 设置 SkuEsModel

  • 发送给 es 保存

2.3.1 公共服务⚠️

  1. 创建用于传输sku库存信息的TO,用于商品上架过程中远程获取库存信息:cn/lzwei/common/to/SkuHasStockTo.java

    @Data
    public class SkuHasStockTo {
        private Long skuId;
        private Boolean hasStock;
    }
    
  2. 修改公共返回类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;
    	}
    }
    
  3. 创建用于传输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;
        }
    }
    
  4. 新增商品上架过程中批量请求elasticsearch失败、商品上架异常的状态枚举对象:cn/lzwei/common/exception/BizCodeEnume.java

    public enum BizCodeEnume {
        PRODUCT_UP_EXCEPTION(11000,"商品上架异常");
    }
    
  5. 新增商品状态的枚举常量类,用于商品上架成功后的状态更新: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 仓储服务⚠️

  1. 创建封装sku库存状态的VO,封装商品上架远程调用通过skuId列表获取sku对应的库存状态:cn/lzwei/bilimall/ware/vo/SkuHasStockVo.java

    @Data
    public class SkuHasStockVo {
        private Long skuId;
        private Boolean hasStock;
    }
    
  2. 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);
        }
    }
    
  3. WareSkuService:商品上架远程调用:通过skuId列表获取sku对应的库存信息

    public interface WareSkuService extends IService<WareSkuEntity> {
        /**
         * 商品上架远程调用:通过skuId列表获取sku对应的库存信息
         */
        List<SkuHasStockVo> getSkusWareInfoBySkuIds(List<Long> skuIds);
    }
    
  4. 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;
        }
    }
    
  5. WareSkuDao.java:获取sku剩余库存:库存量-锁定库存量

    @Mapper
    public interface WareSkuDao extends BaseMapper<WareSkuEntity> {
        //通过skuId获取剩余库存:库存量-锁定库存量
        Long getStock(Long skuId);
    }
    
  6. 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 检索服务⚠️

  1. 创建常量类,保存商品索引常量:cn/lzwei/bilimall/search/constant/EsConstant.java

    public class EsConstant {
        //商品索引
        public static final String PRODUCT_INDEX="product";
    }
    
  2. 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());
            }
        }
    }
    
  3. ProductSaveService

    public interface ProductSaveService {
        /**
         * 保存商品信息
         */
        boolean productStatusUp(List<SpuUpTo> spuUpTo) throws IOException;
    }
    
  4. 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 商品服务💡

  1. 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;
        }
    }
    
  2. SpuInfoService:将所属的sku信息保存到elasticsearch中

    public interface SpuInfoService extends IService<SpuInfoEntity> {
        /**
         * 商品上架:将所属的sku信息保存到elasticsearch中
         */
        R up(Long spuId);
    }
    
  3. 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;
        }
    }
    
  4. AttrService:获取可用于检索的规格参数

    public interface AttrService extends IService<AttrEntity> {
        /**
         * 获取可用于检索的规格参数
         */
        List<Long> getSearchAttr(List<Long> productAttrIds);
    }
    
  5. 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;
        }
    }
    
  6. AttrDao.java:获取可用于检索的规格参数

    @Mapper
    public interface AttrDao extends BaseMapper<AttrEntity> {
        /**
         * 获取可用于检索的规格参数
         */
        List<Long> getSearchAttr(List<Long> productAttrIds);
    }
    
  7. 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>
    
  8. 创建库存服务的远程调用接口: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);
    }
    
  9. 创建索引服务的远程调用接口:cn/lzwei/bilimall/product/feign/SearchFeignService.java

    @FeignClient("bilimall-search")
    public interface SearchFeignService {
        /**
         * 保存商品信息
         */
        @PostMapping("//search/save/product")
        R productStatusUp(@RequestBody List<SpuUpTo> spuUpTo);
    }
    
  10. SpuInfoDao.java:上架成功修改商品状态为上架

    @Mapper
    public interface SpuInfoDao extends BaseMapper<SpuInfoEntity> {
        //远程调用成功:修改商品状态为上架
        void updateSpuStatus(@Param("spuId") Long spuId, @Param("code") Integer code);
    }
    
  11. 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>
    
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愿你满腹经纶

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值