新增商品 Websocket test POST http://localhost:88/api//product/spuinfo/save Description or Example # BUG要点 ## 手动抛出异常的时候, 千万不要捕获! > 如果手动跑出异常又手动捕获了, 异常对于方法而言不可见, 即对于事务而言不可见, 会导致事务永远都无法回滚, 即使出现了异常, **因此, 不能捕获异常** ## 修改SPU描述图片的主键类型 > SPU描述图片的主键类型, 默认与配置文件中的自增一致, 但是如果是自增类型, 那么会忽略传入的spuId, 但是我们的目的就是手动传入spuId, 因此需要改为`IdType.INPUT`类型 ## # 核心代码 ```java @RequestMapping("/save") // @RequiresPermissions("product:spuinfo:save") public R save(@RequestBody @Validated(AddGroup.class) SpuSaveVO spuSaveVO) throws Exception { spuInfoService.saveSpuInfoAndSkuInfo(spuSaveVO); return R.ok(); } ``` ```java @Override @Transactional public void saveSpuInfoAndSkuInfo(SpuSaveVO spuSaveVO) { // 1. 保存SPU基本信息 SpuInfoEntity spuInfoEntity = new SpuInfoEntity(); // SPU基本信息实体类 BeanUtils.copyProperties(spuSaveVO, spuInfoEntity); Date curTime = new Date(); // 创建时间和更新时间都是当前时间 spuInfoEntity.setCreateTime(curTime); spuInfoEntity.setUpdateTime(curTime); this.save(spuInfoEntity); // 获取SPU id, brand id, category id Long spuId = spuInfoEntity.getId(); Long brandId = spuInfoEntity.getBrandId(); Long catalogId = spuInfoEntity.getCatalogId(); // 2. 保存SPU的描述图片, 通过JSR303校验, 一定会有合法元素 List<String> decript = spuSaveVO.getDecript(); // 描述图片集合 String descriptUrls = StringUtils.join(decript, ","); // 把描述图片的地址通过","隔开 SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity().setSpuId(spuId).setDecript(descriptUrls); spuInfoDescService.save(spuInfoDescEntity); // 3. 保存SPU的图片集, 通过JSR303校验, 一定会有合法元素 List<String> images = spuSaveVO.getImages(); // 获取SPU图片集URL List<SpuImagesEntity> spuImagesEntities = images.stream().map(image -> { SpuImagesEntity spuImagesEntity = new SpuImagesEntity(); spuImagesEntity .setSpuId(spuId) .setImgUrl(image); return spuImagesEntity; }).collect(Collectors.toList()); spuImagesService.saveBatch(spuImagesEntities); // 4. 保存SPU规格参数, JSR303校验, 规格参数集合一定不为空 List<BaseAttrs> baseAttrs = spuSaveVO.getBaseAttrs(); List<ProductAttrValueEntity> productAttrValueEntities = baseAttrs.stream().map(baseAttr -> { ProductAttrValueEntity productAttrValueEntity = new ProductAttrValueEntity(); // 规格参数实体类 Long attrId = baseAttr.getAttrId(); // 属性id String attrName = attrService.getBaseMapper() .selectById(attrId) .getAttrName(); // 获取属性的名字 productAttrValueEntity.setSpuId(spuId) .setAttrId(attrId) .setAttrName(attrName) .setQuickShow(baseAttr.getShowDesc()) .setAttrValue(baseAttr.getAttrValues()) .setAttrSort(ProductConstant.Priority.DEFAULT_PRIORITY.getCode()); // 赋予默认优先级 return productAttrValueEntity; }).collect(Collectors.toList()); productAttrValueService.saveBatch(productAttrValueEntities); // 5. 保存SPU的积分信息 Bounds bounds = spuSaveVO.getBounds(); // 获取spu积分信息 SpuBoundTO spuBoundTO = new SpuBoundTO(); BeanUtils.copyProperties(bounds, spuBoundTO); spuBoundTO.setSpuId(spuId); R saveSpuBound = spuBoundsService.save(spuBoundTO); if (saveSpuBound.getCode() != 0L) { throw new RuntimeException("保存SPU积分信息失败"); } // 6. 保存SKU信息 List<Skus> skus = spuSaveVO.getSkus(); // 获取所有的sku, 不可能为空或空数组 skus.forEach(sku -> { String defaultImage = null; // 这里还没有经过校验, 可能为空 List<Images> skuImages = sku.getImages(); if (skuImages != null && !skuImages.isEmpty()) { // 存在图片信息时 // 获取默认图片 List<String> defaultImages = skuImages.stream() .filter(img -> img.getDefaultImg() == 1) // 默认图片的值为1, 找出默认图片 .map(Images::getImgUrl) .collect(Collectors.toList()); if (!defaultImages.isEmpty()) defaultImage = defaultImages.get(0); } // 6.1 保存SKU的基本信息 SkuInfoEntity skuInfoEntity = new SkuInfoEntity(); BeanUtils.copyProperties(sku, skuInfoEntity); skuInfoEntity.setSpuId(spuId) .setBrandId(brandId) .setCatalogId(catalogId) .setSaleCount(0L) // 销售量默认为0 .setSkuDefaultImg(defaultImage); // 保存sku基本信息 skuInfoService.save(skuInfoEntity); // 获取SKU的id Long skuId = skuInfoEntity.getSkuId(); if (skuImages != null && !skuImages.isEmpty()) { // 存在图片信息时 // 6.2 保存SKU的图片信息 List<SkuImagesEntity> skuImagesEntities = skuImages.stream().map(skuImg -> { SkuImagesEntity skuImagesEntity = new SkuImagesEntity(); BeanUtils.copyProperties(skuImg, skuImagesEntity); skuImagesEntity.setSkuId(skuId) .setImgSort(ProductConstant.Priority.DEFAULT_PRIORITY.getCode()); return skuImagesEntity; }).filter(skuImagesEntity -> StringUtils.isNotBlank(skuImagesEntity.getImgUrl())) .collect(Collectors.toList()); // 将所有的空路径的图片都过滤掉 skuImagesService.saveBatch(skuImagesEntities); } // 获取SKU的销售属性 List<Attr> attrs = sku.getAttr(); if (attrs != null && !attrs.isEmpty()) { // 存在销售属性时 List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attrs.stream().map(attr -> { SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity(); BeanUtils.copyProperties(attr, skuSaleAttrValueEntity); skuSaleAttrValueEntity.setSkuId(skuId) .setAttrSort(ProductConstant.Priority.DEFAULT_PRIORITY.getCode()); return skuSaleAttrValueEntity; }).collect(Collectors.toList()); // 6.3 保存SKU销售属性 skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities); } // 获取SKU的折扣信息 SkuDiscountTO skuDiscountTO = new SkuDiscountTO(); BeanUtils.copyProperties(sku, skuDiscountTO); // 调用微服务, 保存这些这些折扣信息 R saveSkuDis = skuFullReductionService.saveDiscount(skuDiscountTO.setSkuId(skuId)); // 调用的同时封装skuId if (saveSkuDis.getCode() != 0L) { // TODO: 权宜之计, 用该方法来替代全局分布式事务, 后面会用SEATA替代 throw new RuntimeException("保存SKU折扣信息失败"); } }); } ``` # 相关说明 ## 图片描述和图片集为什么单独成表? > 描述图片一般会存在很多, 虽然使用了OSS进行图片的存储, 但是数据库仍要存储一大串字符串URL地址, **一旦图片多, 地址长度会变得很长, 即所需要占的字节数变多, 导致数据页的记录变少, 层数变多, 随机IO更多, 效率更低** > 因此, 需要分表, 减少随机IO, 提高查询效率 ## 为什么用逗号存储描述图片, 而不用逗号存储图片集? > 因为描述图片比较单一, 除了URL字段就是SPU的id, 如果分开存储, 会冗余很多SPU id, 因此没必要分记录存储 > 而图片集除了url地址, 还有其他字段, 这些字段都是独立的, 因此需要多条记录, 不可以写到一起 ## 为什么要过滤空路径图片? > 很简单, 不过滤数据库就会有一堆无效记录, 浪费磁盘空间 ## 为什么要进行两次校验? > 因为一个微服务调用另一个微服务, 它们互相都是薛定谔的猫, 都是盲盒, 不能确定对方是否做出了校验, 所以都需要校验, 确保数据是正确的 > 这里我没有用, 我直接使用了JSR303进行了一次校验
# BUG要点 ## 手动抛出异常的时候, 千万不要捕获! > 如果手动跑出异常又手动捕获了, 异常对于方法而言不可见, 即对于事务而言不可见, 会导致事务永远都无法回滚, 即使出现了异常, **因此, 不能捕获异常** ## 修改SPU描述图片的主键类型 > SPU描述图片的主键类型, 默认与配置文件中的自增一致, 但是如果是自增类型, 那么会忽略传入的spuId, 但是我们的目的就是手动传入spuId, 因此需要改为`IdType.INPUT`类型 ## # 核心代码 ```java @RequestMapping("/save") // @RequiresPermissions("product:spuinfo:save") public R save(@RequestBody @Validated(AddGroup.class) SpuSaveVO spuSaveVO) throws Exception { spuInfoService.saveSpuInfoAndSkuInfo(spuSaveVO); return R.ok(); } ``` ```java @Override @Transactional public void saveSpuInfoAndSkuInfo(SpuSaveVO spuSaveVO) { // 1. 保存SPU基本信息 SpuInfoEntity spuInfoEntity = new SpuInfoEntity(); // SPU基本信息实体类 BeanUtils.copyProperties(spuSaveVO, spuInfoEntity); Date curTime = new Date(); // 创建时间和更新时间都是当前时间 spuInfoEntity.setCreateTime(curTime); spuInfoEntity.setUpdateTime(curTime); this.save(spuInfoEntity); // 获取SPU id, brand id, category id Long spuId = spuInfoEntity.getId(); Long brandId = spuInfoEntity.getBrandId(); Long catalogId = spuInfoEntity.getCatalogId(); // 2. 保存SPU的描述图片, 通过JSR303校验, 一定会有合法元素 List<String> decript = spuSaveVO.getDecript(); // 描述图片集合 String descriptUrls = StringUtils.join(decript, ","); // 把描述图片的地址通过","隔开 SpuInfoDescEntity spuInfoDescEntity = new SpuInfoDescEntity().setSpuId(spuId).setDecript(descriptUrls); spuInfoDescService.save(spuInfoDescEntity); // 3. 保存SPU的图片集, 通过JSR303校验, 一定会有合法元素 List<String> images = spuSaveVO.getImages(); // 获取SPU图片集URL List<SpuImagesEntity> spuImagesEntities = images.stream().map(image -> { SpuImagesEntity spuImagesEntity = new SpuImagesEntity(); spuImagesEntity .setSpuId(spuId) .setImgUrl(image); return spuImagesEntity; }).collect(Collectors.toList()); spuImagesService.saveBatch(spuImagesEntities); // 4. 保存SPU规格参数, JSR303校验, 规格参数集合一定不为空 List<BaseAttrs> baseAttrs = spuSaveVO.getBaseAttrs(); List<ProductAttrValueEntity> productAttrValueEntities = baseAttrs.stream().map(baseAttr -> { ProductAttrValueEntity productAttrValueEntity = new ProductAttrValueEntity(); // 规格参数实体类 Long attrId = baseAttr.getAttrId(); // 属性id String attrName = attrService.getBaseMapper() .selectById(attrId) .getAttrName(); // 获取属性的名字 productAttrValueEntity.setSpuId(spuId) .setAttrId(attrId) .setAttrName(attrName) .setQuickShow(baseAttr.getShowDesc()) .setAttrValue(baseAttr.getAttrValues()) .setAttrSort(ProductConstant.Priority.DEFAULT_PRIORITY.getCode()); // 赋予默认优先级 return productAttrValueEntity; }).collect(Collectors.toList()); productAttrValueService.saveBatch(productAttrValueEntities); // 5. 保存SPU的积分信息 Bounds bounds = spuSaveVO.getBounds(); // 获取spu积分信息 SpuBoundTO spuBoundTO = new SpuBoundTO(); BeanUtils.copyProperties(bounds, spuBoundTO); spuBoundTO.setSpuId(spuId); R saveSpuBound = spuBoundsService.save(spuBoundTO); if (saveSpuBound.getCode() != 0L) { throw new RuntimeException("保存SPU积分信息失败"); } // 6. 保存SKU信息 List<Skus> skus = spuSaveVO.getSkus(); // 获取所有的sku, 不可能为空或空数组 skus.forEach(sku -> { String defaultImage = null; // 这里还没有经过校验, 可能为空 List<Images> skuImages = sku.getImages(); if (skuImages != null && !skuImages.isEmpty()) { // 存在图片信息时 // 获取默认图片 List<String> defaultImages = skuImages.stream() .filter(img -> img.getDefaultImg() == 1) // 默认图片的值为1, 找出默认图片 .map(Images::getImgUrl) .collect(Collectors.toList()); if (!defaultImages.isEmpty()) defaultImage = defaultImages.get(0); } // 6.1 保存SKU的基本信息 SkuInfoEntity skuInfoEntity = new SkuInfoEntity(); BeanUtils.copyProperties(sku, skuInfoEntity); skuInfoEntity.setSpuId(spuId) .setBrandId(brandId) .setCatalogId(catalogId) .setSaleCount(0L) // 销售量默认为0 .setSkuDefaultImg(defaultImage); // 保存sku基本信息 skuInfoService.save(skuInfoEntity); // 获取SKU的id Long skuId = skuInfoEntity.getSkuId(); if (skuImages != null && !skuImages.isEmpty()) { // 存在图片信息时 // 6.2 保存SKU的图片信息 List<SkuImagesEntity> skuImagesEntities = skuImages.stream().map(skuImg -> { SkuImagesEntity skuImagesEntity = new SkuImagesEntity(); BeanUtils.copyProperties(skuImg, skuImagesEntity); skuImagesEntity.setSkuId(skuId) .setImgSort(ProductConstant.Priority.DEFAULT_PRIORITY.getCode()); return skuImagesEntity; }).filter(skuImagesEntity -> StringUtils.isNotBlank(skuImagesEntity.getImgUrl())) .collect(Collectors.toList()); // 将所有的空路径的图片都过滤掉 skuImagesService.saveBatch(skuImagesEntities); } // 获取SKU的销售属性 List<Attr> attrs = sku.getAttr(); if (attrs != null && !attrs.isEmpty()) { // 存在销售属性时 List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attrs.stream().map(attr -> { SkuSaleAttrValueEntity skuSaleAttrValueEntity = new SkuSaleAttrValueEntity(); BeanUtils.copyProperties(attr, skuSaleAttrValueEntity); skuSaleAttrValueEntity.setSkuId(skuId) .setAttrSort(ProductConstant.Priority.DEFAULT_PRIORITY.getCode()); return skuSaleAttrValueEntity; }).collect(Collectors.toList()); // 6.3 保存SKU销售属性 skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities); } // 获取SKU的折扣信息 SkuDiscountTO skuDiscountTO = new SkuDiscountTO(); BeanUtils.copyProperties(sku, skuDiscountTO); // 调用微服务, 保存这些这些折扣信息 R saveSkuDis = skuFullReductionService.saveDiscount(skuDiscountTO.setSkuId(skuId)); // 调用的同时封装skuId if (saveSkuDis.getCode() != 0L) { // TODO: 权宜之计, 用该方法来替代全局分布式事务, 后面会用SEATA替代 throw new RuntimeException("保存SKU折扣信息失败"); } }); } ``` # 相关说明 ## 图片描述和图片集为什么单独成表? > 描述图片一般会存在很多, 虽然使用了OSS进行图片的存储, 但是数据库仍要存储一大串字符串URL地址, **一旦图片多, 地址长度会变得很长, 即所需要占的字节数变多, 导致数据页的记录变少, 层数变多, 随机IO更多, 效率更低** > 因此, 需要分表, 减少随机IO, 提高查询效率 ## 为什么用逗号存储描述图片, 而不用逗号存储图片集? > 因为描述图片比较单一, 除了URL字段就是SPU的id, 如果分开存储, 会冗余很多SPU id, 因此没必要分记录存储 > 而图片集除了url地址, 还有其他字段, 这些字段都是独立的, 因此需要多条记录, 不可以写到一起 ## 为什么要过滤空路径图片? > 很简单, 不过滤数据库就会有一堆无效记录, 浪费磁盘空间 ## 为什么要进行两次校验? > 因为一个微服务调用另一个微服务, 它们互相都是薛定谔的猫, 都是盲盒, 不能确定对方是否做出了校验, 所以都需要校验, 确保数据是正确的 > 这里我没有用, 我直接使用了JSR303进行了一次校验