一、发布商品
1.1 获取所有会员等级
配置gateway的路由转发规则,这里我设置了gateway直接从nacos中动态获取配置,以免每次加个路由规则,每次都要重启网关。(路由规则也同步加在了本地application.yml中,nacos中配置文件为route.yaml)
- id: member_routeuri: lb://gulimall-memberpredicates:- Path=/api/gulimallmember/**filters:- RewritePath=/api/(?<segment>.*), /$\{segment}
*解决点击发布商品—不发送请求问题
点击发布商品菜单时,前端应该向后台发送如下请求:
但我这里没有。
解决方法:npm install --save pubsub-js
然后在src下的main.js中导入
import PubSub from 'pubsub-js'Vue.prototype.PubSub = PubSub
1.2 获取分类关联的品牌
在添加SPU信息时,选择分类后会发送请求,查询当前分类下的品牌信息。
http://localhost:88/api/gulimallproduct/categorybrandrelation/brands/list?t=1646998496895&catId=225
Controller只用于处理请求、接收和校验数据
Service接收Controller传来的数据,进行业务处理
Controller接收Service处理完的数据,封装成页面指定VO
package com.atguigu.gulimall.gulimallproduct.vo;import lombok.Data;@Datapublic class BrandVo {private Long brandId;private String brandName;}
/*** 获取当前分类对应的所有品牌*/// /product/categorybrandrelation/brands/list@GetMapping("/brands/list")public R relationBrandsList(@RequestParam(value = "catId") Long catId) {List<BrandEntity> brands = categoryBrandRelationService.getBrands(catId);List<BrandVo> collect = brands.stream().map((brandEntity -> {BrandVo brandVo = new BrandVo();brandVo.setBrandId(brandEntity.getBrandId());brandVo.setBrandName(brandEntity.getName());return brandVo;})).collect(Collectors.toList());return R.ok().put("data", collect);}
声明&实现
@AutowiredCategoryBrandRelationDao categoryBrandRelationDao;@AutowiredBrandService brandService;/*** 获取指定分类对应的所有品牌* @param catId*/@Overridepublic List<BrandEntity> getBrands(Long catId) {// 1 通过分类id取出当前分类与品牌关联的所有记录 查`pms_category_brand_relation`表List<CategoryBrandRelationEntity> categoryBrandRelationEntities = categoryBrandRelationDao.selectList(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));// 2 通过查出来的关联记录,获取每条记录的品牌id,再通过品牌id查询品牌List<BrandEntity> brandEntityList = categoryBrandRelationEntities.stream().map((item) -> {Long brandId = item.getBrandId();BrandEntity brand = brandService.getById(brandId);return brand;}).collect(Collectors.toList());return brandEntityList;}
1.3 获取分类下所有属性分组&关联属性

发布商品在设置规格参数的时候,会向后台发送请求,查询当前分类下对应的所有属性分组以及关联属性。
https://easydoc.net/s/78237135/ZUqEdvA4/6JM6txHf
1、查出当前catelogId对应分类下的所有属性分组
2、查出每个分组的所有关联属性
新建一个vo类,存放需要返回的数据
package com.atguigu.gulimall.gulimallproduct.vo;import com.atguigu.gulimall.gulimallproduct.entity.AttrEntity;import lombok.Data;import java.util.List;@Datapublic class AttrGroupWithAttrsVo {/*** 分组id*/private Long attrGroupId;/*** 组名*/private String attrGroupName;/*** 排序*/private Integer sort;/*** 描述*/private String descript;/*** 组图标*/private String icon;/*** 所属分类id*/private Long catelogId;/*** 所关联的所有属性*/private List<AttrEntity> attrs;}
/*** 获取当前分类下所有的属性分组&关联属性*/// /product/attrgroup/{catelogId}/withattr@GetMapping("/{catelogId}/withattr")public R getAttrGroupWithAttr(@PathVariable("catelogId") Long catelogId) {// 1、查出当前catelogId对应分类下的所有属性分组// 2、查出每个分组的所有关联属性List<AttrGroupWithAttrsVo> vos = attrGroupService.getAttrGroupWithAttrByCatelogId(catelogId);return R.ok().put("data", vos);}
声明&实现
/*** 根据分类id(catelogId)查询当前分类下所有的属性分组&关联属性* @param catelogId* @return*/@Overridepublic List<AttrGroupWithAttrsVo> getAttrGroupWithAttrByCatelogId(Long catelogId) {// 1、查询分组信息// 根据catelogId在`pms_attr_group`中查到分组信息List<AttrGroupEntity> attrGroupEntities = this.list(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));// 将查到的分组基本信息放到AttrGroupWithAttrsVo对象中List<AttrGroupWithAttrsVo> groupWithAttrsVos = attrGroupEntities.stream().map((item) -> {AttrGroupWithAttrsVo attrGroupWithAttrsVo = new AttrGroupWithAttrsVo();BeanUtils.copyProperties(item, attrGroupWithAttrsVo);// 2、查询分组关联所有属性// 之前在AttrService中写过通过attrGroupId查询关联的全部属性,直接调用List<AttrEntity> relationAttr = attrService.getRelationAttr(item.getAttrGroupId());attrGroupWithAttrsVo.setAttrs(relationAttr);return attrGroupWithAttrsVo;}).collect(Collectors.toList());return groupWithAttrsVos;}
1.4 新增商品
设置销售属性
在设置好规格销售属性和SKU信息后,点击下一步。
可以发现浏览器控制台打印了一长串JSON数据,这个就是我们提交给后台需要保存的数据。
1.4.1 JSON生成JAVA实体类
浏览器控制台打印了一长串json数据,复制,使用在线JSON生成JAVA实体类。在线JSON生成JAVA实体类

修改实体类,将id有关属性设置为Long类型,价格有关属性设置为BigDecimal类型;删除get/set方法使用@Data注解。
1.4.2 实现业务逻辑
这里的逻辑比较复杂,涉及到gulimall_pms(product)和gulimall_sms(coupon)中多张表的保存。获取到的SpuSaveVo包含了许多信息,通过以下6步将其信息保存到数据库中(这里强烈建议好好看看每张表里面到底有什么属性)
- 保存spu基本信息 pms_spu_info
- 保存spu的描述图片 pms_spu_info_desc
- 保存spu的图片集 pms_spu_images
- 保存spu的规格参数 pms_product_attr_value
- 保存spu的积分信息 gulimall_sms->sms_spu_bounds 与需要远程调用gulimall-coupon服务
- 保存当前spu对应的所有sku信息
- 保存sku的基本信息 pms_sku_info
- 保存sku的图片信息 pms_sku_images
- 保存sku的销售属性信息 pms_sku_sale_attr_value
- 保存sku的优惠、满减等信息 gulimall_sms(sms_sku_ladder/sms_sku_full_reduction/sms_member_price)
其中保存积分信息和优惠满减信息都需要远程调用coupon服务。
/*** 保存*/@RequestMapping("/save")//@RequiresPermissions("gulimallproduct:spuinfo:save")public R save(@RequestBody SpuSaveVo vo){// spuInfoService.save(spuInfo);spuInfoService.saveSpuInfo(vo);return R.ok();}
service声明&实现(没有实现远程调用)。
@AutowiredSpuInfoDescService infoDescService;@AutowiredSpuImagesService spuImagesService;@AutowiredProductAttrValueService attrValueService;@AutowiredAttrService attrService;@AutowiredSkuInfoService skuInfoService;@AutowiredSkuImagesService skuImagesService;@AutowiredSkuSaleAttrValueService skuSaleAttrValueService;@AutowiredCouponFeignService couponFeignService;@Transactional@Overridepublic void saveSpuInfo(SpuSaveVo vo) {@Transactional@Overridepublic void saveSpuInfo(SpuSaveVo vo) {//1、保存spu基本信息 pms_spu_infoSpuInfoEntity spuInfo = new SpuInfoEntity();BeanUtils.copyProperties(vo, spuInfo);spuInfo.setCreateTime(new Date()); // mybatis-plus设置自动填充注解 @TableField(fill = FieldFill.INSERT)spuInfo.setUpdateTime(new Date()); // mybatis-plus设置自动填充注解 @TableField(fill = FieldFill.INSERT_UPDATE)//this.save(spuInfo); // 为啥不直接使用自带的save方法?this.saveBaseInfo(spuInfo);//2、保存spu的描述图片 pms_spu_info_descList<String> decript = vo.getDecript();SpuInfoDescEntity descEntity = new SpuInfoDescEntity();descEntity.setSpuId(spuInfo.getId()); // 保存后,id会自动封装到实体类中descEntity.setDecript(String.join(",", decript));// infoDescService.save(descEntity); //为啥不直接用infoDescService的save方法。。。infoDescService.saveSpuInfoDesc(descEntity);//3、保存spu的图片集 pms_spu_imagesList<String> images = vo.getImages(); // 获取SpuSaveVo里的图片集spuImagesService.saveImages(spuInfo.getId(), images); // 保存图片集//4、保存spu的规格参数 pms_product_attr_valueList<BaseAttrs> baseAttrs = vo.getBaseAttrs(); // 获取spu的规格参数List<ProductAttrValueEntity> attrValueEntityList = baseAttrs.stream().map((item) -> {ProductAttrValueEntity productAttrValueEntity = new ProductAttrValueEntity();productAttrValueEntity.setAttrId(item.getAttrId());AttrEntity attrEntity = attrService.getById(item.getAttrId());productAttrValueEntity.setAttrName(attrEntity.getAttrName());productAttrValueEntity.setAttrValue(item.getAttrValues());productAttrValueEntity.setQuickShow(item.getShowDesc());productAttrValueEntity.setSpuId(spuInfo.getId());return productAttrValueEntity;}).collect(Collectors.toList());// attrValueService.saveBatch(attrValueEntityList); // 这里直接saveBatch就行attrValueService.saveProductAttr(attrValueEntityList); // 创建新方法也是在新方法中调用saveBatch。。//5、保存spu的积分信息 gulimall_sms->sms_spu_bounds 与需要远程调用gulimall-coupon服务// coupon有一个SpuBoundsController// 查看数据库 我们需要传 spu_id grow_bounds buy_bounds过去//6、保存当前spu对应的所有sku信息// 从vo中获取提交的所有skuList<Skus> skus = vo.getSkus();if (!CollectionUtils.isEmpty(skus)) { // 一定要判断不为空skus.forEach(item -> { // 遍历每个skuVo对象,将其中的信息保存//6.1 保存sku的基本信息 pms_sku_infoSkuInfoEntity skuInfoEntity = new SkuInfoEntity();// 只有skuName、skuTitle、skuSubtitle、price可以直接拷贝BeanUtils.copyProperties(item, skuInfoEntity);skuInfoEntity.setBrandId(spuInfo.getBrandId()); // brandIdskuInfoEntity.setCatalogId(spuInfo.getCatalogId()); //catelogIdskuInfoEntity.setSaleCount(0L); // saleCountskuInfoEntity.setSpuId(spuInfo.getId()); // spuId// 寻找每个sku的默认图片String defaultImag = "";for (Images image : item.getImages()) {// 每个Images对象中有一个defaultImg属性,如果为1则表示该图片为默认图片if (image.getDefaultImg() == 1) {defaultImag = image.getImgUrl();}}skuInfoEntity.setSkuDefaultImg(defaultImag); // 默认图片从sku的images中获取//skuInfoService.save(skuInfoEntity);skuInfoService.saveSkuInfo(skuInfoEntity); // 保存SKU信息,这样sku的自增主键就出来了//6.2 保存sku的图片信息 pms_sku_images// sku_id img_url default_imgLong skuId = skuInfoEntity.getSkuId();// 必须要先保存sku,获得skuId后,才能保存图片,因为 pms_sku_images表中需要用到skuIdList<Images> skuImages = item.getImages(); // 从当前sku中获取图片集// 每个sku图片集中的每个图片Url都会构成pms_sku_images表中的一条记录List<SkuImagesEntity> skuImagesEntityList = skuImages.stream().map((im) -> {SkuImagesEntity skuImagesEntity = new SkuImagesEntity();skuImagesEntity.setSkuId(skuId);skuImagesEntity.setImgUrl(im.getImgUrl());skuImagesEntity.setDefaultImg(im.getDefaultImg());return skuImagesEntity;}).collect(Collectors.toList());skuImagesService.saveBatch(skuImagesEntityList);//6.3 保存sku的销售属性信息 pms_sku_sale_attr_value// sku_id attr_id attr_name attr_value attr_sortList<Attr> attr = item.getAttr();List<SkuSaleAttrValueEntity> attrValueEntities = attr.stream().map(a -> {SkuSaleAttrValueEntity saleAttrValue = new SkuSaleAttrValueEntity();BeanUtils.copyProperties(a, saleAttrValue);saleAttrValue.setSkuId(skuId);return saleAttrValue;}).collect(Collectors.toList());skuSaleAttrValueService.saveBatch(attrValueEntities);//6.4 保存sku的优惠、满减等信息 gulimall_sms->sms_sku_ladder/sms_sku_full_reduction/sms_member_price// 这里需要远程调用 gulimall-coupon服务});}}/*** 保存spu的基本信息** @param spuInfo*/@Overridepublic void saveBaseInfo(SpuInfoEntity spuInfo) {this.baseMapper.insert(spuInfo);}}
/*** 保存spu的描述信息* @param descEntity*/@Overridepublic void saveSpuInfoDesc(SpuInfoDescEntity descEntity) {this.baseMapper.insert(descEntity);}
/*** 保存spu的图片信息* @param id* @param images*/@Overridepublic void saveImages(Long id, List<String> images) {if (!CollectionUtils.isEmpty(images)) {List<SpuImagesEntity> spuImagesEntityList = images.stream().map((image) -> {SpuImagesEntity spuImagesEntity = new SpuImagesEntity();spuImagesEntity.setSpuId(id);spuImagesEntity.setImgName(image);return spuImagesEntity;}).collect(Collectors.toList());this.saveBatch(spuImagesEntityList);}}
@Overridepublic void saveProductAttr(List<ProductAttrValueEntity> attrValueEntityList) {this.saveBatch(attrValueEntityList);}
@Overridepublic void saveSkuInfo(SkuInfoEntity skuInfoEntity) {this.baseMapper.insert(skuInfoEntity);}
远程调用回顾
可以回看之前远程调用的笔记:https://www.yuque.com/mrlinxi/pxvr4g/rdcgap#ijC15
- 调用方Service编写一个接口,告诉SpringCloud这个接口需要调用远程服务(一般会放在feign这个包下),会使用到@FeignClient(“需要调用的微服务名”)注解
- 这里定义方法接口的方法签名一定要(不一样也可以)和微服务提供者的Controller中调用的方法签名(最好直接复制controller里面的方法)一致。
- 如果需要传递参数,那么
@RequestParam和@RequestBody@PathVariable等不能省略,必需加。
- Controller层定义一个与Service层对应的方法
- 调用方在主启动类开启远程调用功能 @EnableFeignClient(basePackages=”包路径”)—默认扫描默认会扫描注解所在包的当前包以及子包下的@FeignCilent。
将所有需要远程调用的Service接口都写在feign包下。
product主启动类加上@EnableFeignClients注解,并指明包路径
@SpringBootApplication@EnableDiscoveryClient@MapperScan(value = "com.atguigu.gulimall.gulimallproduct.dao")@EnableFeignClients(basePackages = "com.atguigu.gulimall.gulimallproduct.feign")public class GulimallProductApplication {public static void main(String[] args) {SpringApplication.run(GulimallProductApplication.class, args);}}
保存spu积分信息远程调用
我们需要将spu_id grow_bounds buy_bounds 传输到gulimall-coupon服务,让其存储到gulimall_sms->sms_spu_bounds表中。
现在product服务要给coupon传输数据,我们可以将这些数据封装成一个对象。在SpringCloud中,会默认把封装的这个对象转成JSON数据进行传输,那么coupon服务会收到一个JSON数据。
那么我们可以在common的to包下中定义一个TO(数据传输对象——用于不同的应用程序之间传输)
@Datapublic class SpuBoundsTo {private Long spuId;/*** 成长积分*/private BigDecimal growBounds;/*** 购物积分*/private BigDecimal buyBounds;}
修改gulimall-coupon的SpuBoundsController,将/save请求对应的方法改成@PostMapping
/*** 保存*/@PostMapping("/save")//@RequiresPermissions("gulimallcoupon:spubounds:save")public R save(@RequestBody SpuBoundsEntity spuBounds){spuBoundsService.save(spuBounds);return R.ok();}
gulimall-product的feign包下,创建一个Service接口,用于远程调用coupon服务。
注意:@PostMapping注解中一定要写完整的请求路径(SpuBoundsController的类路径+方法路径)
package com.atguigu.gulimall.gulimallproduct.feign;import com.atguigu.common.to.SpuBoundsTo;import com.atguigu.common.utils.R;import org.springframework.cloud.openfeign.FeignClient;import org.springframework.web.bind.annotation.PostMapping;import org.springframework.web.bind.annotation.RequestBody;@FeignClient("gulimall-coupon")public interface CouponFeignService {/*** CouponFeignService.saveSpuBounds(spuBoundsTo) 调用方法,传了一个对象* 1)有@RequestBody注解 会将这个对象转为Json* 2)找到gulimall-coupon服务,给服务发送/gulimallcoupon/spubounds/save,将上一步转的Json放在请求体位置,发送请求* 3)对方服务收到请求,收到了请求体中的Json数据* (@RequestBody SpuBoundsEntity spuBounds) :将请求体中的Json转成SpuBoundsEntity* 前提是spuBoundsTo中的属性名与SpuBoundsEntity中属性名能对应(spuBoundsTo的属性名是SpuBoundsEntity属性名的子集)* 只要Json数据模型时兼容的,双方服务无需使用同一个to入参* @param spuBoundsTo* @return*/@PostMapping("/gulimallcoupon/spubounds/save")R saveSpuBounds(@RequestBody SpuBoundsTo spuBoundsTo);}
这里我们发现CouponFeignService中方法的入参与SpuBoundsController中对应的方法名以及入参不一致。
方法名不一致其实并不影响调用,只是平时为了方便,直接将需要调用的额方法签名复制过来。
这里着重讲一下,为什么入参可以不同:
CouponFeignService.saveSpuBounds(spuBoundsTo) 调用方法,传了一个对象,这个对象是SpuBoundsTo类,传输流程:
- 有@RequestBody注解 会将这个对象转为Json
- 找到gulimall-coupon服务,给服务发送/gulimallcoupon/spubounds/save,将上一步转的Json放在请求体位置,发送请求
- 对方服务收到请求,收到了请求体中的Json数据
- (@RequestBody SpuBoundsEntity spuBounds) :将请求体中的Json转成SpuBoundsEntity
- a的前提是spuBoundsTo中的属性名与SpuBoundsEntity中属性名能对应(spuBoundsTo的属性名是SpuBoundsEntity属性名的子集)
总结:只要Json数据模型时兼容的,双方服务无需使用同一个to入参
SpuInfoServiceImpl中调用(这里只展示了代码片段,完整版看后面)
//5、保存spu的积分信息 gulimall_sms->sms_spu_bounds 与需要远程调用gulimall-coupon服务// coupon有一个SpuBoundsController// 查看数据库 我们需要传 spu_id grow_bounds buy_bounds过去Bounds bounds = vo.getBounds(); // Bounds类中有 grow_bounds buy_bounds过去SpuBoundsTo spuBoundsTo = new SpuBoundsTo();BeanUtils.copyProperties(bounds, spuBoundsTo);spuBoundsTo.setSpuId(spuInfo.getId());couponFeignService.saveSpuBounds(spuBoundsTo);
保存sku的优惠、满减等信息
common中新建一个SkuReductionTo to类
@Datapublic class SkuReductionTo {private Long skuId;// 打折信息private int fullCount;private BigDecimal discount;private int countStatus;private BigDecimal fullPrice;private BigDecimal reducePrice;private int priceStatus;private List<MemberPrice> memberPrice;}
CouponFeignService,声明调用gulimall-coupon的方法
@PostMapping("/gulimallcoupon/skufullreduction/saveinfo")R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
gulimall-coupon SkuFullReductionController中创建对应方法
@PostMapping("/saveinfo")//@RequiresPermissions("gulimallcoupon:skufullreduction:list")public R saveInfo(@RequestBody SkuReductionTo skuReductionTo){skuFullReductionService.saveSkuReduction(skuReductionTo);return R.ok();}
声明&实现
@AutowiredSkuLadderService skuLadderService;@AutowiredMemberPriceService memberPriceService;@Overridepublic void saveSkuReduction(SkuReductionTo skuReductionTo) {// 6.4 保存sku的优惠、满减、会员价格等信息 gulimall_sms->sms_sku_ladder/sms_sku_full_reduction/sms_member_price// 1、保存数量满减打折优惠信息 sms_sku_ladder (sku_id full_count discount price add_other)SkuLadderEntity skuLadderEntity = new SkuLadderEntity();skuLadderEntity.setSkuId(skuReductionTo.getSkuId()); // skuIdskuLadderEntity.setFullCount(skuReductionTo.getFullCount()); //满几件skuLadderEntity.setDiscount(skuReductionTo.getDiscount()); // 打几折skuLadderEntity.setAddOther(skuReductionTo.getCountStatus()); // 是否与其他优惠叠加skuLadderService.save(skuLadderEntity);// 2、保存 满多少金额,减多少钱 优惠信息// sms_sku_full_reduction (sku_id full_price reduce_price add_other)SkuFullReductionEntity skuFullReductionEntity = new SkuFullReductionEntity();BeanUtils.copyProperties(skuReductionTo, skuFullReductionEntity); // 传过来的对象包含了金额满减的信息,且属性名对应直接copythis.save(skuFullReductionEntity);// 3、保存 会员价格// sms_member_price (sku_id member_level_id member_level_name member_price add_other)List<MemberPrice> memberPrice = skuReductionTo.getMemberPrice(); // 获取当前sku不同会员价格信息List<MemberPriceEntity> memberPriceEntities = memberPrice.stream().map(member -> {MemberPriceEntity memberPriceEntity = new MemberPriceEntity();memberPriceEntity.setSkuId(skuReductionTo.getSkuId()); // 获取skuIdmemberPriceEntity.setMemberLevelId(member.getId()); // 获取会员等级IdmemberPriceEntity.setMemberLevelName(member.getName()); // 获取会员等级名memberPriceEntity.setMemberPrice(member.getPrice()); // 会员价格memberPriceEntity.setAddOther(1); // 是否可与其他优惠叠加return memberPriceEntity;}).collect(Collectors.toList());memberPriceService.saveBatch(memberPriceEntities);}
在SpuInfoServiceImpl中调用,这里只展示了针对当前业务的代码片段,最终完整代码看后面
//6.4 保存sku的优惠、满减会员价格等信息 gulimall_sms->sms_sku_ladder/sms_sku_full_reduction/sms_member_price// 这里需要远程调用 gulimall-coupon服务SkuReductionTo skuReductionTo = new SkuReductionTo();BeanUtils.copyProperties(item, skuReductionTo); // 直接把当前遍历的Sku拷贝过去skuReductionTo.setSkuId(skuInfoEntity.getSkuId()); // 先前保存了sku,生成了一个skuId,设置skuIdcouponFeignService.saveSkuReduction(skuReductionTo);
其他改动
远程调用之后我们需要验证一下是否成功。在R中新增一个获得code的方法(R继承了HashMap)
public Integer getCode() {return Integer.parseInt((String) this.get("code"));}
分别在SpuInfoServiceImpl里两个远程调用的地方判断一下是否成功。
SpuInfoServiceImpl全部实现
@Transactional@Overridepublic void saveSpuInfo(SpuSaveVo vo) {//1、保存spu基本信息 pms_spu_infoSpuInfoEntity spuInfo = new SpuInfoEntity();BeanUtils.copyProperties(vo, spuInfo);spuInfo.setCreateTime(new Date()); // mybatis-plus设置自动填充注解 @TableField(fill = FieldFill.INSERT)spuInfo.setUpdateTime(new Date()); // mybatis-plus设置自动填充注解 @TableField(fill = FieldFill.INSERT_UPDATE)//this.save(spuInfo); // 为啥不直接使用自带的save方法?this.saveBaseInfo(spuInfo);//2、保存spu的描述图片 pms_spu_info_descList<String> decript = vo.getDecript();SpuInfoDescEntity descEntity = new SpuInfoDescEntity();descEntity.setSpuId(spuInfo.getId()); // 保存后,id会自动封装到实体类中descEntity.setDecript(String.join(",", decript));// infoDescService.save(descEntity); //为啥不直接用infoDescService的save方法。。。infoDescService.saveSpuInfoDesc(descEntity);//3、保存spu的图片集 pms_spu_imagesList<String> images = vo.getImages(); // 获取SpuSaveVo里的图片集spuImagesService.saveImages(spuInfo.getId(), images); // 保存图片集//4、保存spu的规格参数 pms_product_attr_valueList<BaseAttrs> baseAttrs = vo.getBaseAttrs(); // 获取spu的规格参数List<ProductAttrValueEntity> attrValueEntityList = baseAttrs.stream().map((item) -> {ProductAttrValueEntity productAttrValueEntity = new ProductAttrValueEntity();productAttrValueEntity.setAttrId(item.getAttrId());AttrEntity attrEntity = attrService.getById(item.getAttrId());productAttrValueEntity.setAttrName(attrEntity.getAttrName());productAttrValueEntity.setAttrValue(item.getAttrValues());productAttrValueEntity.setQuickShow(item.getShowDesc());productAttrValueEntity.setSpuId(spuInfo.getId());return productAttrValueEntity;}).collect(Collectors.toList());// attrValueService.saveBatch(attrValueEntityList); // 这里直接saveBatch就行attrValueService.saveProductAttr(attrValueEntityList); // 创建新方法也是在新方法中调用saveBatch。。//5、保存spu的积分信息 gulimall_sms->sms_spu_bounds 与需要远程调用gulimall-coupon服务// coupon有一个SpuBoundsController// 查看数据库 我们需要传 spu_id grow_bounds buy_bounds过去Bounds bounds = vo.getBounds(); // Bounds类中有 grow_bounds buy_bounds过去SpuBoundsTo spuBoundsTo = new SpuBoundsTo();BeanUtils.copyProperties(bounds, spuBoundsTo);spuBoundsTo.setSpuId(spuInfo.getId());R r = couponFeignService.saveSpuBounds(spuBoundsTo);if (r.getCode() != 0) {log.error("远程调用gulimall-coupon保存spu的积分信息失败...............");}//6、保存当前spu对应的所有sku信息// 从vo中获取提交的所有skuList<Skus> skus = vo.getSkus();if (!CollectionUtils.isEmpty(skus)) { // 一定要判断不为空skus.forEach(item -> { // 遍历每个skuVo对象,将其中的信息保存//6.1 保存sku的基本信息 pms_sku_infoSkuInfoEntity skuInfoEntity = new SkuInfoEntity();// 只有skuName、skuTitle、skuSubtitle、price可以直接拷贝BeanUtils.copyProperties(item, skuInfoEntity);skuInfoEntity.setBrandId(spuInfo.getBrandId()); // brandIdskuInfoEntity.setCatalogId(spuInfo.getCatalogId()); //catelogIdskuInfoEntity.setSaleCount(0L); // saleCountskuInfoEntity.setSpuId(spuInfo.getId()); // spuId// 寻找每个sku的默认图片String defaultImag = "";for (Images image : item.getImages()) {// 每个Images对象中有一个defaultImg属性,如果为1则表示该图片为默认图片if (image.getDefaultImg() == 1) {defaultImag = image.getImgUrl();}}skuInfoEntity.setSkuDefaultImg(defaultImag); // 默认图片从sku的images中获取//skuInfoService.save(skuInfoEntity);skuInfoService.saveSkuInfo(skuInfoEntity); // 保存SKU信息,这样sku的自增主键就出来了//6.2 保存sku的图片信息 pms_sku_images// sku_id img_url default_imgLong skuId = skuInfoEntity.getSkuId();// 必须要先保存sku,获得skuId后,才能保存图片,因为 pms_sku_images表中需要用到skuIdList<Images> skuImages = item.getImages(); // 从当前sku中获取图片集// 每个sku图片集中的每个图片Url都会构成pms_sku_images表中的一条记录List<SkuImagesEntity> skuImagesEntityList = skuImages.stream().map((im) -> {SkuImagesEntity skuImagesEntity = new SkuImagesEntity();skuImagesEntity.setSkuId(skuId);skuImagesEntity.setImgUrl(im.getImgUrl());skuImagesEntity.setDefaultImg(im.getDefaultImg());return skuImagesEntity;}).collect(Collectors.toList());skuImagesService.saveBatch(skuImagesEntityList);//6.3 保存sku的销售属性信息 pms_sku_sale_attr_value// sku_id attr_id attr_name attr_value attr_sortList<Attr> attr = item.getAttr();List<SkuSaleAttrValueEntity> attrValueEntities = attr.stream().map(a -> {SkuSaleAttrValueEntity saleAttrValue = new SkuSaleAttrValueEntity();BeanUtils.copyProperties(a, saleAttrValue);saleAttrValue.setSkuId(skuId);return saleAttrValue;}).collect(Collectors.toList());skuSaleAttrValueService.saveBatch(attrValueEntities);//6.4 保存sku的满减(数量满减&金额满减)、会员价格等信息 gulimall_sms->sms_sku_ladder/sms_sku_full_reduction/sms_member_price// 这里需要远程调用 gulimall-coupon服务SkuReductionTo skuReductionTo = new SkuReductionTo();BeanUtils.copyProperties(item, skuReductionTo); // 直接把当前遍历的Sku拷贝过去skuReductionTo.setSkuId(skuInfoEntity.getSkuId()); // 先前保存了sku,生成了一个skuId,设置skuIdR r1 = couponFeignService.saveSkuReduction(skuReductionTo);if (r1.getCode() != 0) {log.error("远程调用gulimall-coupon保存sku的满减(数量满减&金额满减)、会员价格失败...............");}});}}
1.4.3 测试
因为微服务很多,所以可以设置一下每个服务的内存占用。
创建一个Compound,将需要频繁重启的服务加进去,就可以批量重启。再设置每个微服务最大占用内存

以debug模式启动product服务,前端提交数据
先报存spu的基本信息,查看数据库,发现并没有保存。这是因为我们开启了事务,而Mysql的默认事务隔离级别是可重复读。
我们设置一下mysql的隔离界别为读未提交:SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;
现在pms_spu_info表中可以看到spu的信息
视频里在pms_spu_info_desc表中新建记录的时候报错(我实操没有报错),因为pms_spu_info_desc表中的spuId不是自增主键,而是手动进行输入的,所以需要修改SpuInfoDescEntity的spuId属性:
因为我用的nacos2.0.3,注册跟配置都没有问题,结果在远程调用的时候报错。这个没有找到解决方法(重启nacos就好了。。。。)
ERROR 24636 —- [t.remote.worker] c.a.n.c.remote.client.grpc.GrpcClient : Server check fail, please check server localhost ,port 9848 is available , error ={}
远程超时问题:在product设置一下超时时间
#设置feign客户端超时时间(OpenFeign默认支持ribbon)ribbon:#指的是建立连接后从服务器读取到可用资源所用的时间 msReadTimeout: 5000#指的是建立连接所用的时间,适用于网络状况正常的情况下,两端连接所用的时间 msConnectTimeout: 5000
sku_images中保存了没有选中的图片:(TODO没有图片路径的无须保存)
同样,满减信息里面满0元减0元,满0件不打折也都不需要存储。


出现这个错误是因为我们在后台debug的时间太长,超时了。
spu_bounds表没有回滚
sku_images不保存没有图片路径的信息
通过filter函数式接口过滤
//6.2 保存sku的图片信息 pms_sku_images// sku_id img_url default_imgLong skuId = skuInfoEntity.getSkuId();// 必须要先保存sku,获得skuId后,才能保存图片,因为 pms_sku_images表中需要用到skuIdList<Images> skuImages = item.getImages(); // 从当前sku中获取图片集// 每个sku图片集中的每个图片Url都会构成pms_sku_images表中的一条记录List<SkuImagesEntity> skuImagesEntityList = skuImages.stream().map((im) -> {SkuImagesEntity skuImagesEntity = new SkuImagesEntity();skuImagesEntity.setSkuId(skuId);skuImagesEntity.setImgUrl(im.getImgUrl());skuImagesEntity.setDefaultImg(im.getDefaultImg());return skuImagesEntity;}).filter(emtity -> {// filter中,返回true就是保留,返回false就是剔除return !StringUtils.isEmpty(emtity.getImgUrl());}).collect(Collectors.toList());skuImagesService.saveBatch(skuImagesEntityList);
满减信息过滤
在保存sku优惠信息的时候,只需要保存存在的件数折扣、金额满减、会员价格。所以需要一个判断。(视频里面没有hashMemberPrice,会导致当没有件数折扣和金额满减时无法保存会员信息)
最开是我给hashMemberPrice传的是skuReductionTo.getMemberPrice() 但是一直报类型转换异常,没办法只能传Item的MemberPrice了。
//6.4 保存sku的满减(数量满减&金额满减)、会员价格等信息 gulimall_sms->sms_sku_ladder/sms_sku_full_reduction/sms_member_price// 这里需要远程调用 gulimall-coupon服务SkuReductionTo skuReductionTo = new SkuReductionTo();BeanUtils.copyProperties(item, skuReductionTo); // 直接把当前遍历的Sku拷贝过去skuReductionTo.setSkuId(skuInfoEntity.getSkuId()); // 先前保存了sku,生成了一个skuId,设置skuId// 当存在多件打折信息或者金额满减信息时,满减信息才会保存,同时需要考虑没有满减信息但存在会员价格的情况if (hasMemberPrice(item.getMemberPrice()) || skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(BigDecimal.ZERO) > 0) {R r1 = couponFeignService.saveSkuReduction(skuReductionTo);if (r1.getCode() != 0) {log.error("远程调用gulimall-coupon保存sku的满减(数量满减&金额满减)、会员价格失败...............");}}/*** 判断会员价格是否存在* @param memberPrices* @return*/private boolean hasMemberPrice(List<com.atguigu.gulimall.gulimallproduct.vo.spu.MemberPrice> memberPrices) {for (com.atguigu.gulimall.gulimallproduct.vo.spu.MemberPrice memberPrice : memberPrices) {if (memberPrice.getPrice().compareTo(BigDecimal.ZERO) > 0) return true;}return false;}
修改后的代码
这里修改主要是针对优惠信息的bug进行修改。
@Transactional@Overridepublic void saveSpuInfo(SpuSaveVo vo) {//1、保存spu基本信息 pms_spu_infoSpuInfoEntity spuInfo = new SpuInfoEntity();BeanUtils.copyProperties(vo, spuInfo);spuInfo.setCreateTime(new Date()); // mybatis-plus设置自动填充注解 @TableField(fill = FieldFill.INSERT)spuInfo.setUpdateTime(new Date()); // mybatis-plus设置自动填充注解 @TableField(fill = FieldFill.INSERT_UPDATE)//this.save(spuInfo); // 为啥不直接使用自带的save方法?this.saveBaseInfo(spuInfo);//2、保存spu的描述图片 pms_spu_info_descList<String> decript = vo.getDecript();SpuInfoDescEntity descEntity = new SpuInfoDescEntity();descEntity.setSpuId(spuInfo.getId()); // 保存后,id会自动封装到实体类中descEntity.setDecript(String.join(",", decript));// infoDescService.save(descEntity); //为啥不直接用infoDescService的save方法。。。infoDescService.saveSpuInfoDesc(descEntity);//3、保存spu的图片集 pms_spu_imagesList<String> images = vo.getImages(); // 获取SpuSaveVo里的图片集spuImagesService.saveImages(spuInfo.getId(), images); // 保存图片集//4、保存spu的规格参数 pms_product_attr_valueList<BaseAttrs> baseAttrs = vo.getBaseAttrs(); // 获取spu的规格参数List<ProductAttrValueEntity> attrValueEntityList = baseAttrs.stream().map((item) -> {ProductAttrValueEntity productAttrValueEntity = new ProductAttrValueEntity();productAttrValueEntity.setAttrId(item.getAttrId());AttrEntity attrEntity = attrService.getById(item.getAttrId());productAttrValueEntity.setAttrName(attrEntity.getAttrName());productAttrValueEntity.setAttrValue(item.getAttrValues());productAttrValueEntity.setQuickShow(item.getShowDesc());productAttrValueEntity.setSpuId(spuInfo.getId());return productAttrValueEntity;}).collect(Collectors.toList());// attrValueService.saveBatch(attrValueEntityList); // 这里直接saveBatch就行attrValueService.saveProductAttr(attrValueEntityList); // 创建新方法也是在新方法中调用saveBatch。。//5、保存spu的积分信息 gulimall_sms->sms_spu_bounds 与需要远程调用gulimall-coupon服务// coupon有一个SpuBoundsController// 查看数据库 我们需要传 spu_id grow_bounds buy_bounds过去Bounds bounds = vo.getBounds(); // Bounds类中有 grow_bounds buy_bounds过去SpuBoundsTo spuBoundsTo = new SpuBoundsTo();BeanUtils.copyProperties(bounds, spuBoundsTo);spuBoundsTo.setSpuId(spuInfo.getId());R r = couponFeignService.saveSpuBounds(spuBoundsTo);if (r.getCode() != 0) {log.error("远程调用gulimall-coupon保存spu的积分信息失败...............");}//6、保存当前spu对应的所有sku信息// 从vo中获取提交的所有skuList<Skus> skus = vo.getSkus();if (!CollectionUtils.isEmpty(skus)) { // 一定要判断不为空skus.forEach(item -> { // 遍历每个skuVo对象,将其中的信息保存//6.1 保存sku的基本信息 pms_sku_infoSkuInfoEntity skuInfoEntity = new SkuInfoEntity();// 只有skuName、skuTitle、skuSubtitle、price可以直接拷贝BeanUtils.copyProperties(item, skuInfoEntity);skuInfoEntity.setBrandId(spuInfo.getBrandId()); // brandIdskuInfoEntity.setCatalogId(spuInfo.getCatalogId()); //catelogIdskuInfoEntity.setSaleCount(0L); // saleCountskuInfoEntity.setSpuId(spuInfo.getId()); // spuId// 寻找每个sku的默认图片String defaultImag = "";for (Images image : item.getImages()) {// 每个Images对象中有一个defaultImg属性,如果为1则表示该图片为默认图片if (image.getDefaultImg() == 1) {defaultImag = image.getImgUrl();}}skuInfoEntity.setSkuDefaultImg(defaultImag); // 默认图片从sku的images中获取//skuInfoService.save(skuInfoEntity);skuInfoService.saveSkuInfo(skuInfoEntity); // 保存SKU信息,这样sku的自增主键就出来了//6.2 保存sku的图片信息 pms_sku_images// sku_id img_url default_imgLong skuId = skuInfoEntity.getSkuId();// 必须要先保存sku,获得skuId后,才能保存图片,因为 pms_sku_images表中需要用到skuIdList<Images> skuImages = item.getImages(); // 从当前sku中获取图片集// 每个sku图片集中的每个图片Url都会构成pms_sku_images表中的一条记录List<SkuImagesEntity> skuImagesEntityList = skuImages.stream().map((im) -> {SkuImagesEntity skuImagesEntity = new SkuImagesEntity();skuImagesEntity.setSkuId(skuId);skuImagesEntity.setImgUrl(im.getImgUrl());skuImagesEntity.setDefaultImg(im.getDefaultImg());return skuImagesEntity;}).filter(emtity -> {// filter中,返回true就是保留,返回false就是剔除return !StringUtils.isEmpty(emtity.getImgUrl());}).collect(Collectors.toList());skuImagesService.saveBatch(skuImagesEntityList);//6.3 保存sku的销售属性信息 pms_sku_sale_attr_value// sku_id attr_id attr_name attr_value attr_sortList<Attr> attr = item.getAttr();List<SkuSaleAttrValueEntity> attrValueEntities = attr.stream().map(a -> {SkuSaleAttrValueEntity saleAttrValue = new SkuSaleAttrValueEntity();BeanUtils.copyProperties(a, saleAttrValue);saleAttrValue.setSkuId(skuId);return saleAttrValue;}).collect(Collectors.toList());skuSaleAttrValueService.saveBatch(attrValueEntities);//6.4 保存sku的满减(数量满减&金额满减)、会员价格等信息 gulimall_sms->sms_sku_ladder/sms_sku_full_reduction/sms_member_price// 这里需要远程调用 gulimall-coupon服务SkuReductionTo skuReductionTo = new SkuReductionTo();BeanUtils.copyProperties(item, skuReductionTo); // 直接把当前遍历的Sku拷贝过去skuReductionTo.setSkuId(skuInfoEntity.getSkuId()); // 先前保存了sku,生成了一个skuId,设置skuId// 当存在多件打折信息或者金额满减信息时,满减信息才会保存,同时需要考虑没有满减信息但存在会员价格的情况if (hasMemberPrice(item.getMemberPrice()) || skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(BigDecimal.ZERO) > 0) {R r1 = couponFeignService.saveSkuReduction(skuReductionTo);if (r1.getCode() != 0) {log.error("远程调用gulimall-coupon保存sku的满减(数量满减&金额满减)、会员价格失败...............");}}});}}
@Overridepublic void saveSkuReduction(SkuReductionTo skuReductionTo) {// 6.4 保存sku的优惠、满减、会员价格等信息 gulimall_sms->sms_sku_ladder/sms_sku_full_reduction/sms_member_price// 1、保存数量满减打折优惠信息 sms_sku_ladder (sku_id full_count discount price add_other)SkuLadderEntity skuLadderEntity = new SkuLadderEntity();if (skuReductionTo.getFullCount() > 0) { // 存在数量满减才保存skuLadderEntity.setSkuId(skuReductionTo.getSkuId()); // skuIdskuLadderEntity.setFullCount(skuReductionTo.getFullCount()); //满几件skuLadderEntity.setDiscount(skuReductionTo.getDiscount());skuLadderEntity.setAddOther(skuReductionTo.getCountStatus()); // 是否与其他优惠叠加skuLadderService.save(skuLadderEntity);}// 2、保存 满多少金额,减多少钱 优惠信息// sms_sku_full_reduction (sku_id full_price reduce_price add_other)SkuFullReductionEntity skuFullReductionEntity = new SkuFullReductionEntity();if (skuReductionTo.getFullPrice().compareTo(BigDecimal.ZERO) > 0) { // 存在金额满减才保存BeanUtils.copyProperties(skuReductionTo, skuFullReductionEntity); // 传过来的对象包含了金额满减的信息,且属性名对应直接copythis.save(skuFullReductionEntity);}// 3、保存 会员价格// sms_member_price (sku_id member_level_id member_level_name member_price add_other)List<MemberPrice> memberPrice = skuReductionTo.getMemberPrice(); // 获取当前sku不同会员价格信息List<MemberPriceEntity> memberPriceEntities = memberPrice.stream().map(member -> {MemberPriceEntity memberPriceEntity = new MemberPriceEntity();memberPriceEntity.setSkuId(skuReductionTo.getSkuId()); // 获取skuIdmemberPriceEntity.setMemberLevelId(member.getId()); // 获取会员等级IdmemberPriceEntity.setMemberLevelName(member.getName()); // 获取会员等级名memberPriceEntity.setMemberPrice(member.getPrice()); // 会员价格memberPriceEntity.setAddOther(1); // 是否可与其他优惠叠加return memberPriceEntity;}).filter(memberPriceEntity -> memberPriceEntity.getMemberPrice().compareTo(BigDecimal.ZERO) == 1).collect(Collectors.toList());memberPriceService.saveBatch(memberPriceEntities);}
暂未解决的问题
sms_spu_bounds表无法回滚,分布式事务的问题有待解决。// 高级部分完善
视频中没有判断MemberPrice,导致当没有件数折扣和金额满减时无法保存会员信息(我这里修复了这个bug)
二、spu管理
2.1 spu检索

https://easydoc.net/s/78237135/ZUqEdvA4/9LISLvy7 spu检索接口文档
/*** 列表*/@RequestMapping("/list")//@RequiresPermissions("gulimallproduct:spuinfo:list")public R list(@RequestParam Map<String, Object> params){// PageUtils page = spuInfoService.queryPage(params);PageUtils page = spuInfoService.queryPageByCondition(params);return R.ok().put("page", page);}
声明&实现
@Overridepublic PageUtils queryPageByCondition(Map<String, Object> params) {QueryWrapper<SpuInfoEntity> wrapper = new QueryWrapper<>();String key = (String) params.get("key");// SELECT * FROM pms_spu_info WHEREif (!StringUtils.isEmpty(key)) {wrapper.and((w) -> {w.eq("id", key).or().like("spu_name", key);});}// status = 1 and (id = 1 or spu_name like xxx)String status = (String) params.get("status");if (!StringUtils.isEmpty(status)) {wrapper.eq("publish_status", status);}String brandId = (String) params.get("brandId");if (!StringUtils.isEmpty(key) && !"0".equalsIgnoreCase(brandId)) { // 判断不为0,为保证初始化状态下能查到所有数据wrapper.eq("brand_id", brandId);}String catelogId = (String) params.get("catelogId");if (!StringUtils.isEmpty(key) && !"0".equalsIgnoreCase(brandId)) { // 判断不为0,为保证初始化状态下能查到所有数据wrapper.eq("catalog_id", catelogId); // 这里数据库是catalog_id 可能是打错了}IPage<SpuInfoEntity> page = this.page(new Query<SpuInfoEntity>().getPage(params), wrapper);return new PageUtils(page);}
检索成功实现,但是可以发现时间戳的格式不符合规范。
想要符合规范,可以通过配置文件中配置spring.jackson.date-format,这样我们写出的日期数据,均会按照指定的格式进行格式化
spring:jackson:date-format: yyyy-MM-dd HH:mm:ss
2.2 spu规格维护
这里视频P100才讲 跳转:https://www.yuque.com/mrlinxi/pxvr4g/zsn9l9#aKisn
三、商品管理
https://easydoc.net/s/78237135/ZUqEdvA4/ucirLq1D 商品管理检索的是sku的信息
/*** 列表 按照检索条件对sku进行分页查询*/@RequestMapping("/list")//@RequiresPermissions("gulimallproduct:skuinfo:list")public R list(@RequestParam Map<String, Object> params){// PageUtils page = skuInfoService.queryPage(params);PageUtils page = skuInfoService.queryPageByCondition(params);return R.ok().put("page", page);}
/*** 按照检索条件对sku进行分页查询** @param params* @return*/@Overridepublic PageUtils queryPageByCondition(Map<String, Object> params) {QueryWrapper<SkuInfoEntity> wrapper = new QueryWrapper<>();/*** key: 华为* catelogId: 225* brandId: 9* min: 500* max: 10000*/String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.and(w -> {w.eq("sku_id", key).or().like("sku_name", key);});}String catelogId = (String) params.get("catelogId");if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)) { // 判断不为0,为保证初始化状态下能查到所有数据wrapper.eq("catalog_id", catelogId);}String brandId = (String) params.get("brandId");if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) { // 判断不为0,为保证初始化状态下能查到所有数据wrapper.eq("brand_id", brandId);}String min = (String) params.get("min");if (!StringUtils.isEmpty(min)) {wrapper.ge("price", min); // ge表示大于等于}String max = (String) params.get("max");if (!StringUtils.isEmpty(max)) {try {BigDecimal maxPrice = new BigDecimal(max);if (maxPrice.compareTo(BigDecimal.ZERO) > 0) {wrapper.le("price", max); // le表示小于等于}} catch (Exception e) {e.printStackTrace();}}IPage<SkuInfoEntity> page = this.page(new Query<SkuInfoEntity>().getPage(params),wrapper);return new PageUtils(page);}
