商品维护——新增商品

电商品台新增商品的过程大致如下:

56.jpg

另外为了测试,我还手动新增了一些测试数据:

57.jpg


一、新增会员、仓储、订单、优惠券等模块前端页面

1、将会员、仓储、订单、优惠券等模块注册进nacos并正常启动

58.jpg

此时nacos中的服务列表:

59.jpg

2、使用网关服务,对会员、仓储、订单、优惠券等模块进行路由转发:

spring:
  cloud:
    gateway:
      routes:
        - id: product_route
          uri: lb://zidanmall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/(?<segment>/?.*), /$\{segment}

        - id: coupon_route
          uri: lb://zidanmall-coupon
          predicates:
            - Path=/api/coupon/**
          filters:
            - RewritePath=/api/(?<segment>/?.*), /$\{segment}

        - id: member_route
          uri: lb://zidanmall-member
          predicates:
            - Path=/api/member/**
          filters:
            - RewritePath=/api/(?<segment>/?.*), /$\{segment}

        - id: order_route
          uri: lb://zidanmall-order
          predicates:
            - Path=/api/order/**
          filters:
            - RewritePath=/api/(?<segment>/?.*), /$\{segment}

        - id: ware_route
          uri: lb://zidanmall-ware
          predicates:
            - Path=/api/ware/**
          filters:
            - RewritePath=/api/(?<segment>/?.*), /$\{segment}

        - id: third_party_route
          uri: lb://zidanmall-third-party
          predicates:
            - Path=/api/thirdparty/**
          filters:
            - RewritePath=/api/thirdparty/(?<segment>/?.*), /$\{segment}

        - id: admin_route
          uri: lb://renren-fast
          predicates:
            - Path=/api/**
          filters:
            - RewritePath=/api/(?<segment>/?.*), /renren-fast/$\{segment}


二、新增商品——1——基本信息

1、在用户系统——会员等级下,添加几条会员等级数据:

60.jpg

2、新增商品基本信息第一步:需要“根据所选分类查询品牌列表”接口:

   GET    /product/categorybrandrelation/brands/list   

Controller层接口:

@GetMapping("/brands/list")
public R relationBrandsList(@RequestParam(value = "catId", required = true) Long catId){
    List<BrandEntity> brandEntities = categoryBrandRelationService.getBrandsByCatId(catId);
    List<BrandVo> data = null;
    if (!CollectionUtils.isEmpty(brandEntities)){
        data = brandEntities.stream().map(b -> {
            BrandVo brandVo = new BrandVo();
            brandVo.setBrandId(b.getBrandId());
            brandVo.setBrandName(b.getName());
            return brandVo;
        }).collect(Collectors.toList());
    }
    return R.ok().put("data", data);
}

到这,第一步“基本信息”的添加就通顺了;

61.jpg


三、新增商品——2——规格参数

获取制定三级分类下的所有“属性分组”以及“关联的基本属性”

  GET   /product/attrgroup/{catelogId}/withattr  

需要相应的数据结构如下:

{
	"msg": "success",
	"code": 0,
	"data": [{
		"attrGroupId": 1,
		"attrGroupName": "主体",
		"sort": 0,
		"descript": "主体",
		"icon": "dd",
		"catelogId": 225,
		"attrs": [{
			"attrId": 7,
			"attrName": "入网型号",
			"searchType": 1,
			"valueType": 0,
			"icon": "xxx",
			"valueSelect": "aaa;bb",
			"attrType": 1,
			"enable": 1,
			"catelogId": 225,
			"showDesc": 1,
			"attrGroupId": null
			}, {
			"attrId": 8,
			"attrName": "上市年份",
			"searchType": 0,
			"valueType": 0,
			"icon": "xxx",
			"valueSelect": "2018;2019",
			"attrType": 1,
			"enable": 1,
			"catelogId": 225,
			"showDesc": 0,
			"attrGroupId": null
			}]
		}]
}

1、Service层核心代码实现:

@Override
public List<AttrGroupWithAttrVo> getAttrGroupWithAttrsByCatelogId(Long catelogId) {
    //获取catelogId对于的所有分组列表
    List<AttrGroupEntity> groups = this.baseMapper.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
    if (CollectionUtils.isEmpty(groups)){
        return null;
    }
    //填充对应的attrs
    return groups.stream().map(g -> {
        AttrGroupWithAttrVo attrGroupWithAttrVo = new AttrGroupWithAttrVo();
        BeanUtils.copyProperties(g, attrGroupWithAttrVo);
        List<AttrAttrgroupRelationEntity> relations = attrAttrgroupRelationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", g.getAttrGroupId()));
        if (!CollectionUtils.isEmpty(relations)) {
            List<Long> collect = relations.stream().map(AttrAttrgroupRelationEntity::getAttrId).collect(Collectors.toList());
            List<AttrEntity> attrs = attrDao.selectList(new QueryWrapper<AttrEntity>().in("attr_id", collect));
            attrGroupWithAttrVo.setAttrs(attrs);
        }
        return attrGroupWithAttrVo;
    }).collect(Collectors.toList());
}

2、页面效果:

62.jpg

其中的快速展示选择框,会继承“规格参数”创建时候的默认值,但是在这里我们还可以去掉;“去掉的话,只影响当前商品”


四、新增商品——完整过程

1、添加基本信息:

63.jpg

2、添加规格参数:

64.jpg

3、添加销售属性:

65.jpg

4、添加SKU信息(笛卡尔积列表)

66.jpg

5、点击提交:被提交的数据会以一个“超级JSON”提交到后台,后台需要花一定的时间去储存这部分数据:

67.jpg

对于这些复杂数据,我们需要专门准备一个大步骤描述;


五、新增商品——后端接口存储复杂商品数据

1、分析提交的数据:我们使用在线json工具格式化,并自动生成java类:

https://www.bejson.com/?src=xiaof 

https://www.bejson.com/json2javapojo/new/

68.jpg

下载自动生成的java类,并将对于的Id的类型修改为Long,价格类的类型修改为BigDecimal

2、商品新增业务逻辑分析——分析如下:

//1、保存Spu基本信息; pms_spu_info

//2、保存Spu的描述图片; pms_spu_info_desc

//3、保存Spu的图片集; pms_spu_images

//4、保存Spu的规格参数; pms_product_attr_value

//5、保存Spu的积分信息;——跨服务调用:zidanmall-coupon:sms_spu_bounds

//6、保存当前Spu对应的所有Sku信息;

       //6.1、保存Sku的基本信息; pms_sku_info

       //6.2、保存Sku的图片信息; pms_sku_images

       //6.3、保存Sku的销售属性信息; pms_sku_sale_attr_value

       //6.4、保存Sku的优惠、满减等信息;——跨服务调用:zidanmall-coupon:sms_sku_ladder(打折表)–>sms_sku_full_reduction(满减表)–>sms_member_price(会员价格表)

3、开始编写新增逻辑:

SpuInfoController.java:

@RequestMapping("/save")
public R save(@RequestBody SpuSaveVo vo){
    spuInfoService.saveSpuInfo(vo);
    return R.ok();
}

SpuInfoServiceImpl.java:

@Transactional
@Override
public void saveSpuInfo(SpuSaveVo vo) {
        //1、保存spu基本信息 pms_spu_info
        SpuInfoEntity infoEntity = new SpuInfoEntity();
        BeanUtils.copyProperties(vo,infoEntity);
        infoEntity.setCreateTime(new Date());
        infoEntity.setUpdateTime(new Date());
        this.baseMapper.insert(infoEntity);

        //2、保存Spu的描述图片 pms_spu_info_desc
        List<String> decript = vo.getDecript();
        SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
        descEntity.setSpuId(infoEntity.getId());
        descEntity.setDecript(String.join(",",decript));
        spuInfoDescService.saveSpuInfoDesc(descEntity);

        //3、保存spu的图片集 pms_spu_images
        List<String> images = vo.getImages();
        imagesService.saveImages(infoEntity.getId(),images);

        //4、保存spu的规格参数;pms_product_attr_value
        List<BaseAttrs> baseAttrs = vo.getBaseAttrs();
        List<ProductAttrValueEntity> collect = baseAttrs.stream().map(attr -> {
            ProductAttrValueEntity valueEntity = new ProductAttrValueEntity();
            valueEntity.setAttrId(attr.getAttrId());
            AttrEntity id = attrService.getById(attr.getAttrId());
            valueEntity.setAttrName(id.getAttrName());
            valueEntity.setAttrValue(attr.getAttrValues());
            valueEntity.setQuickShow(attr.getShowDesc());
            valueEntity.setSpuId(infoEntity.getId());
            return valueEntity;
        }).collect(Collectors.toList());
        attrValueService.saveProductAttr(collect);

        //5、保存spu的积分信息;zidanmall_sms->sms_spu_bounds
        Bounds bounds = vo.getBounds();
        SpuBoundTo spuBoundTo = new SpuBoundTo();
        BeanUtils.copyProperties(bounds,spuBoundTo);
        spuBoundTo.setSpuId(infoEntity.getId());
        R r = couponFeignService.saveSpuBounds(spuBoundTo);
        if(r.getCode() != 0){
            log.error("远程保存spu积分信息失败");
        }

        //6、保存当前spu对应的所有sku信息;
        List<Skus> skus = vo.getSkus();
        if(skus!=null && skus.size()>0){
            skus.forEach(item->{
                String defaultImg = "";
                for (Images image : item.getImages()) {
                    if(image.getDefaultImg() == 1){
                        defaultImg = image.getImgUrl();
                    }
                }
                SkuInfoEntity skuInfoEntity = new SkuInfoEntity();
                BeanUtils.copyProperties(item,skuInfoEntity);
                skuInfoEntity.setBrandId(infoEntity.getBrandId());
                skuInfoEntity.setCatalogId(infoEntity.getCatalogId());
                skuInfoEntity.setSaleCount(0L);
                skuInfoEntity.setSpuId(infoEntity.getId());
                skuInfoEntity.setSkuDefaultImg(defaultImg);
                //6.1)、sku的基本信息;pms_sku_info
                skuInfoService.saveSkuInfo(skuInfoEntity);

                Long skuId = skuInfoEntity.getSkuId();

                List<SkuImagesEntity> imagesEntities = item.getImages().stream().map(img -> {
                    SkuImagesEntity skuImagesEntity = new SkuImagesEntity();
                    skuImagesEntity.setSkuId(skuId);
                    skuImagesEntity.setImgUrl(img.getImgUrl());
                    skuImagesEntity.setDefaultImg(img.getDefaultImg());
                    return skuImagesEntity;
                }).filter(entity->StringUtils.isNotBlank(entity.getImgUrl())).collect(Collectors.toList());
                //6.2)、sku的图片信息;pms_sku_image
                skuImagesService.saveBatch(imagesEntities);
                //TODO 没有图片路径的无需保存

                List<Attr> attr = item.getAttr();
                List<SkuSaleAttrValueEntity> skuSaleAttrValueEntities = attr.stream().map(a -> {
                    SkuSaleAttrValueEntity attrValueEntity = new SkuSaleAttrValueEntity();
                    BeanUtils.copyProperties(a, attrValueEntity);
                    attrValueEntity.setSkuId(skuId);

                    return attrValueEntity;
                }).collect(Collectors.toList());
                //6.3)、sku的销售属性信息:pms_sku_sale_attr_value
                skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);

                //6.4)、sku的优惠、满减等信息;zidanmall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
                SkuReductionTo skuReductionTo = new SkuReductionTo();
                BeanUtils.copyProperties(item,skuReductionTo);
                skuReductionTo.setSkuId(skuId);
                if(skuReductionTo.getFullCount() >0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1){
                    R r1 = couponFeignService.saveSkuReduction(skuReductionTo);
                    if(r1.getCode() != 0){
                        log.error("远程保存sku优惠信息失败");
                    }
                }
            });
        }
}

其中:5.0、6.4两节都需要调用远程服务 zidanmall_sms ,相关的 Feign 接口如下:

CouponFeignService.java:

@FeignClient("zidanmall-coupon")
public interface CouponFeignService {
    /**
     * 1、CouponFeignService.saveSpuBounds(spuBoundTo);
     *      1)、@RequestBody将这个对象转为json。
     *      2)、找到gulimall-coupon服务,给/coupon/spubounds/save发送请求。
     *          将上一步转的json放在请求体位置,发送请求;
     *      3)、对方服务收到请求。请求体里有json数据。
     *          (@RequestBody SpuBoundsEntity spuBounds);将请求体的json转为SpuBoundsEntity;
     * 只要json数据模型是兼容的。双方服务无需使用同一个to
     * @param spuBoundTo
     * @return
     */
    @PostMapping("/coupon/spubounds/save")
    R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);


    @PostMapping("/coupon/skufullreduction/saveinfo")
    R saveSkuReduction(@RequestBody SkuReductionTo skuReductionTo);
}

4、其中zidanmall_sms服务中的相关service层代码如下:

SkuFullReductionServiceImpl.java:

@Transactional
@Override
public void saveSkuReduction(SkuReductionTo reductionTo) {
    //1、// //6.4)、sku的优惠、满减等信息;gulimall_sms->sms_sku_ladder\sms_sku_full_reduction\sms_member_price
    //sms_sku_ladder
    SkuLadderEntity skuLadderEntity = new SkuLadderEntity();
    skuLadderEntity.setSkuId(reductionTo.getSkuId());
    skuLadderEntity.setFullCount(reductionTo.getFullCount());
    skuLadderEntity.setDiscount(reductionTo.getDiscount());
    skuLadderEntity.setAddOther(reductionTo.getCountStatus());
    if(reductionTo.getFullCount() > 0){
        skuLadderService.save(skuLadderEntity);
    }

    //2、sms_sku_full_reduction
    SkuFullReductionEntity reductionEntity = new SkuFullReductionEntity();
    BeanUtils.copyProperties(reductionTo,reductionEntity);
    if(reductionEntity.getFullPrice().compareTo(new BigDecimal("0"))==1){
        this.save(reductionEntity);
    }

    //3、sms_member_price
    List<MemberPrice> memberPrice = reductionTo.getMemberPrice();
    List<MemberPriceEntity> collect = memberPrice.stream().map(item -> {
        MemberPriceEntity priceEntity = new MemberPriceEntity();
        priceEntity.setSkuId(reductionTo.getSkuId());
        priceEntity.setMemberLevelId(item.getId());
        priceEntity.setMemberLevelName(item.getName());
        priceEntity.setMemberPrice(item.getPrice());
        priceEntity.setAddOther(1);
        return priceEntity;
    }).filter(item-> item.getMemberPrice().compareTo(new BigDecimal("0")) == 1).collect(Collectors.toList());

    memberPriceService.saveBatch(collect);
}

其实此处的ServiceImpl等代码我们虽然添加了事务,但是都是本地事务;

在后续的环节中,我将会加上 seata,以实现分布式事务;


六、新增商品——添加两条数据:

Spu管理

69.jpg

商品管理:——即Sku管理

70.jpg


七、商品管理——Spu检索:

  GET   /product/spuinfo/list  

1、根据上面的截图,我们可以看到,Spu检索,需要支持额外的几个字段搜索:

{
   page: 1,//当前页码
   limit: 10,//每页记录数
   sidx: 'id',//排序字段
   order: 'asc/desc',//排序方式
   key: '华为',//检索关键字      ——额外
   catelogId: 6,//三级分类id     ——额外
   brandId: 1,//品牌id           ——额外
   status: 0,//商品状态          ——额外
}

SpuInfoServiceImpl.java:

@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
    QueryWrapper<SpuInfoEntity> wrapper = new QueryWrapper<>();
    String key = (String) params.get("key");
    if (StringUtils.isNotBlank(key)){
        wrapper.and(obj -> obj.eq("id", key).or().like("spu_name", key));
    }

    String catelogId = (String) params.get("catelogId");
    if (StringUtils.isNotBlank(catelogId) && !"0".equals(catelogId)){
        wrapper.eq("catalog_id", catelogId);
    }

    String brandId = (String) params.get("brandId");
    if (StringUtils.isNotBlank(brandId) && !"0".equals(brandId)){
        wrapper.eq("brand_id", brandId);
    }

    String status = (String)params.get("status");
    if (StringUtils.isNotBlank(status)){
        wrapper.eq("publish_status", status);
    }

    IPage<SpuInfoEntity> page = this.page(new Query<SpuInfoEntity>().getPage(params), wrapper);
    return new PageUtils(page);
}

2、另外,我们得时间格式不好看,我们希望统一为:

  yyyy-MM-dd HH:mm:ss  

只需要增加一个配置项:

spring: 
  jackson:
    date-format: yyyy-MM-dd HH:mm:ss

效果如下:

71.jpg


八、商品管理——Sku检索:

  GET   /product/skuinfo/list  

由上面的截图可以看到,Sku检索,需要支持额外的几个字段搜索:

{
    page: 1,//当前页码
    limit: 10,//每页记录数
    sidx: 'id',//排序字段
    order: 'asc/desc',//排序方式
    key: '华为',//检索关键字
    catelogId: 0,
    brandId: 0,
    min: 0,
    max: 0
}

SkuInfoServiceImpl.java:

@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {
    QueryWrapper<SkuInfoEntity> wrapper = new QueryWrapper<>();
    String key = (String) params.get("key");
    if (StringUtils.isNotBlank(key)){
        wrapper.and(obj -> obj.eq("sku_id", key).or().like("sku_name", key));
    }

    String catelogId = (String) params.get("catelogId");
    if (StringUtils.isNotBlank(catelogId) && !"0".equals(catelogId)){
        wrapper.eq("catalog_id", catelogId);
    }

    String brandId = (String) params.get("brandId");
    if (StringUtils.isNotBlank(brandId) && !"0".equals(brandId)){
        wrapper.eq("brand_id", brandId);
    }

    String min = (String) params.get("min");
    if (StringUtils.isNotBlank(min)){
        wrapper.ge("price", min);
    }

    String max = (String) params.get("max");
    if (StringUtils.isNotBlank(max)){
        try {
            BigDecimal bigDecimal = new BigDecimal(max);
            if (bigDecimal.compareTo(BigDecimal.ZERO) == 1){
                wrapper.le("price", max);
            }
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    IPage<SkuInfoEntity> page = this.page(new Query<SkuInfoEntity>().getPage(params), wrapper);
    return new PageUtils(page);
}

效果如下:

72.jpg

到这里,商品新增(Spu、Sku管理)的内容就都完成了;


九、Spu规格维护

1、在Spu管理界面,我们点击具体的Spu的规格按钮,可以重新修改Spu的规格参数

94.jpg

首先获取当前的Spu规则属性信息:

  GET   /product/attr/base/listforspu/{spuId}  

AttrController.java:

@GetMapping("/base/listforspu/{spuId}")
public R baseAttrListForSpu(@PathVariable("spuId") Long spuId){
    List<ProductAttrValueEntity> data = productAttrValueService.baseAttrListForSpu(spuId);
    return R.ok().put("data", data);
}

回显效果:

95.jpg

2、修改后保存更新:

  POST   /product/attr/update/{spuId}  

[
    {
        "attrId":7,
        "attrName":"入网型号",
        "attrValue":"LIO-AL00",
        "quickShow":1
    },
    {
        "attrId":14,
        "attrName":"机身材质工艺",
        "attrValue":"玻璃",
        "quickShow":0
    },
    {
        "attrId":16,
        "attrName":"CPU型号",
        "attrValue":"HUAWEI Kirin 980",
        "quickShow":1
    }
]

ProductAttrValueServiceImpl.java:

@Transactional
@Override
public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities) {
    //我们不需要去判断哪些值发生了变化,哪些值没有变化,直接将spuId对应的记录全部删除后,再新增即可;
    this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id", spuId));
    //插入操作
    entities.forEach(e -> e.setSpuId(spuId));
    this.saveBatch(entities);
}

jiguiquan@163.com

文章作者信息...

留下你的评论

*评论支持代码高亮<pre class="prettyprint linenums">代码</pre>

相关推荐