《谷粒商城》开发实录:070-100 商品服务-发布商品

070 准备工作

1 名词解释:SPU&SKU

SPU Standard Product Unit (标准化产品单元)
是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一
个产品的特性。
SKU Stock Keeping Unit (库存量单位)
即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。 SKU 这是对于大型连锁超市
DC (配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每
种产品均对应有唯一的 SKU 号。

2 前端代码+数据库脚本

百度网盘:提取码2239

3 生成系统菜单

在数据库gulimall-admin中执行SQL脚本文件sys_menus.sql,生成系统菜单。

4 导入前端代码 

将下载的前端代码复制到前端项目renren-fast-vue中,风险操作注意备份。

5 接口文档

查看接口文档

071-074 属性分组

涉及业务表:

  • pms_attr_group 属性分组表
  • pms_attr 属性表
  • pms_attr_attrgroup_relation 属性-属性分组关系表

例如,对一部手机而言,它可以有:

  • 属性分组:机身
  • 属性分组:芯片
  • 属性:机身长度
  • 属性:机身颜色
  • 属性:芯片品牌
  • 属性-属性分组:机身-机身长度
  • 属性-属性分组:机身-机身颜色
  • 属性-属性分组:芯片-芯片品牌

关于属性分组的基本增删改查略。

商品分类是三级分类的形式,下面是一套分类回写逻辑:

entity/AttrGroupEntity:

// 数据库中不存在的属性
@TableField(exist = false)
private Long[] catelogPath;

service/CategoryService:

// 获取分类路径
Long[] findCatelogPath(Long catelogId);

service/impl/AttrGroupServiceImpl:

@Override
public Long[] findCatelogPath(Long catelogId) {
    List paths = new ArrayList<>();
    List parentPath = findParentPath(catelogId, paths);
    Collections.reverse(parentPath);

    return parentPath.toArray(new Long[parentPath.size()]);
}

// 递归
private List findParentPath(Long catelogId, List paths) {
    paths.add(catelogId);
    CategoryEntity entity = this.getById(catelogId);
    if (entity.getParentCid() != 0) {
        findParentPath(entity.getParentCid(), paths);
    }
    return paths;
}

bug:三级分类后面跟着一张空白页

《谷粒商城》开发实录:070-100 商品服务-发布商品_第1张图片

原因:第三级分类的children属性是一个空集合。

解决方法:只有当children字段不为空时,才会被放入返回前端页面的json数据中。

《谷粒商城》开发实录:070-100 商品服务-发布商品_第2张图片

075 品牌管理

1 模糊查询

一图流:

2 数据库冗余字段同步

对数据库中冗余字段的修改,必须全部更新,或者全部不更新。

所以要在方法前加上事务注解:@Transactional

076-082 平台属性

1 规格参数

增删改查略。

这里有一个bug,请将前端文件attr-add-or-update.vue中的值类型相关代码注释掉,因为在数据库中创建表时这个字段缺失了。不想改表结构了。

《谷粒商城》开发实录:070-100 商品服务-发布商品_第3张图片

2 销售属性

增删改查略。

值得一提的是,在gulimall-common服务中,创建了一个常量类constant/ProductConstant,用来维护gulimall-product服务中的常量。

public class ProductConstant {
    public enum AttrEnum {
        ATTR_TYPE_BASE(1, "基本属性"), ATTR_TYPE_SALE(0, "销售属性");

        private int code;

        private String msg;

        AttrEnum(int code, String msg) {
            this.code = code;
            this.msg = msg;
        }

        public int getCode() {
            return code;
        }

        public String getMsg() {
            return msg;
        }
    }
}

3 属性分组

关联规格参数,增删改查略。

083-092 发布商品

在做这一部分之前,建议先把070-2后端代码中product/vo文件夹导入gulimall-product服务。

1 PubSub is not defined

进入发布商品页面会出现一个PubSub is not defined的问题。

解决方法:在前端项目中

  1. npm install --save pubsub-js
  2. 在src下的main.js中添加代码(如果在070-4导入前端代码时导入了main.js,跳过这一步
    ① import PubSub from 'pubsub-js'
    ② Vue.prototype.PubSub = PubSub

2 会员等级

解决了PubSub is not defined问题,再次进入发布商品页面,前端会向后端发送一个获取会员等级的请求:http://localhost:88/api/member/memberlevel/list,涉及到了会员服务gulimall-member。

启动gulimall-member服务。

在网关服务gulimall-gateway的配置文件application.yml中配置路由,这次我们将所有之前没有配置的服务补充完整:(注意缩进)

spring:
  cloud:    
    gateway:
      routes:
        - id: product_route
          uri: lb://gulimall-product
          predicates:
            - Path=/api/product/**
          filters:
            - RewritePath=/api/?(?.*), /$\{segment} #去掉/api 前缀
        - id: member_route
          uri: lb://gulimall-member
          predicates:
            - Path=/api/member/**
          filters:
            - RewritePath=/api/?(?.*), /$\{segment}
        - id: coupon_route
          uri: lb://gulimall-coupon
          predicates:
            - Path=/api/coupon/**
          filters:
            - RewritePath=/api/?(?.*), /$\{segment}
        - id: ware_route
          uri: lb://gulimall-ware
          predicates:
            - Path=/api/ware/**
          filters:
            - RewritePath=/api/?(?.*), /$\{segment}
        - id: order_route
          uri: lb://gulimall-order
          predicates:
            - Path=/api/order/**
          filters:
            - RewritePath=/api/?(?.*), /$\{segment}
        - id: third_party_route
          uri: lb://gulimall-third-party
          predicates:
            - Path=/api/thirdparty/**
          filters:
            - RewritePath=/api/thirdparty/?(?.*), /$\{segment}
        # 默认路由
        - id: admin_route
          uri: lb://renren-fast #lb:负载均衡
          predicates:
            - Path=/api/** #前端项目发送的请求带有/api 前缀
          filters:
            - RewritePath=/api/?(?.*), /renren-fast/$\{segment} #将/api 前缀改写成/renren-fast

重启gulimall-gateway服务。

随意添加一些会员等级。

3 规格参数

创建接口:根据分类id查询属性及属性分组。原理是在pms_attr 属性表和pms_attr_group 属性分组表中都包含一个catelog_id字段,代码略。

随意添加一些规格参数。

4 分类与品牌的级联关系

在gulimall-product服务中:

新建vo/BrandVo:

@Data
public class BrandVo {
    // 品牌id
    private Long brandId;

    // 品牌名称
    private String brandName;
}

controller/CategoryBrandRelationController:

@GetMapping("/brands/list")
public R relationBrandsList(@RequestParam(value = "catId") Long catId) {
    List vos = categoryBrandRelationService.getBrandsByCatId(catId);
    List brandVoList = vos.stream().map(item -> {
        BrandVo brandVo = new BrandVo();
        brandVo.setBrandId(item.getBrandId());
        brandVo.setBrandName(item.getName());

        return brandVo;
    }).collect(Collectors.toList());

    return R.ok().put("data", brandVoList);
}

service/CategoryBrandRelationService:

List getBrandsByCatId(Long catId);

service/impl/CategoryBrandRelationServiceImpl:

@Override
public List getBrandsByCatId(Long catId) {
    List catelogId = relationDao.selectList(new QueryWrapper().eq("catelog_id", catId));
    List entityList = catelogId.stream().map(item -> {
        Long brandId = item.getBrandId();
        BrandEntity entity = brandService.getById(brandId);

        return entity;
    }).collect(Collectors.toList());

    return entityList;
}

分类与品牌的级联关系创建完成。

5 发布商品

按照页面提示输入商品基本信息:

选择规格参数:

选择销售属性:

编辑SKU信息:

6 保存商品信息

这个功能,业务繁琐但并不复杂:

1 保存spu基本信息: pms_spu_info
2 保存spu的描述图片: pms_spu_info_desc
3 保存spu的图片集: pms_spu_images
4 保存spu的规格参数: pms_product_attr_value
5 保存spu的积分信息: gulimall_sms -> 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的优惠、满减等信息: gulimall_sms -> sms_sku_ladder
                                                      sms_sku_full_reduction
                                                      sms_member_price

保存商品信息的操作是事务性的,如果想在debug过程中看到数据库中的实时数据变化,可以设置当前会话的事务隔离级别为读未提交:

SET SESSION TRANSACTION ISOLATION LEVEL READ UNCOMMITTED;

在此隔离级别下,数据库允许脏读。

controller/SpuInfoController:

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

    return R.ok();
}

service/SpuInfoService:

void saveSpuInfo(SpuSaveVo vo);

void saveBaseSpuInfo(SpuInfoEntity infoEntity);

service/impl/SpuInfoServiceImpl:

@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.saveBaseSpuInfo(infoEntity);

    // 2 保存spu的描述图片: pms_spu_info_desc
    List 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 images = vo.getImages();
    spuImagesService.saveImages(infoEntity.getId(), images);

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

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

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

            //   6.2 sku的图片信息: pms_sku_images
            Long skuId = skuInfoEntity.getSkuId();
            List 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 -> {
                return !StringUtils.isEmpty(entity.getImgUrl());
            }).collect(Collectors.toList());
            skuImagesService.saveBatch(imagesEntities);

            //   6.3 sku的销售属性信息: pms_sku_sale_attr_value
            List attr = item.getAttr();
            List skuSaleAttrValueEntities = attr.stream().map(a -> {
                SkuSaleAttrValueEntity attrValueEntity = new SkuSaleAttrValueEntity();
                BeanUtils.copyProperties(a, attrValueEntity);
                attrValueEntity.setSkuId(skuId);
                return attrValueEntity;
            }).collect(Collectors.toList());
            skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);

            //   6.4 sku的优惠、满减等信息: gulimall_sms -> sms_sku_ladder
            //                                           sms_sku_full_reduction
            //                                           sms_member_price
            SkuReductionTo skuReductionTo = new SkuReductionTo();
            BeanUtils.copyProperties(item, skuReductionTo);
            skuReductionTo.setSkuId(skuId);
            List memberPrices = new ArrayList<>();
            item.getMemberPrice().forEach(memberPrice -> {
                        com.atguigu.common.to.MemberPrice mp = new com.atguigu.common.to.MemberPrice();
                        mp.setId(memberPrice.getId());
                        mp.setName(memberPrice.getName());
                        mp.setPrice(memberPrice.getPrice());
                        memberPrices.add(mp);
                    }
            );
            skuReductionTo.setMemberPrice(memberPrices);
            if (skuReductionTo.getFullCount() > 0 || skuReductionTo.getFullPrice().compareTo(new BigDecimal("0")) == 1) {
                R saveSkuReduction = couponFeignService.saveSkuReduction(skuReductionTo);
                if (saveSkuReduction.getCode() != 0) {
                    log.error("远程保存sku优惠信息失败");
                }
            }
        });
    }
}

@Override
public void saveBaseSpuInfo(SpuInfoEntity infoEntity) {
    this.baseMapper.insert(infoEntity);
}

// 2 保存spu的描述图片: pms_spu_info_desc

entity/SpuInfoDescEntity:

@TableId(type = IdType.INPUT)
private Long spuId;

service/SpuInfoDescService:

void saveSpuInfoDesc(SpuInfoDescEntity descEntity);

service/impl/SpuInfoDescServiceImpl:

@Override
public void saveSpuInfoDesc(SpuInfoDescEntity descEntity) {
    this.baseMapper.insert(descEntity);
}

// 3 保存spu的图片集: pms_spu_images

service/SpuImagesService:

void saveImages(Long id, List images);

service/impl/SpuImagesServiceImpl:

@Override
public void saveImages(Long id, List images) {
    if (images == null || images.size() == 0) {
        return;
    } else {
        List entityList = images.stream().map(img -> {
            SpuImagesEntity spuImagesEntity = new SpuImagesEntity();
            spuImagesEntity.setSpuId(id);
            spuImagesEntity.setImgUrl(img);
            return spuImagesEntity;
        }).collect(Collectors.toList());
        this.saveBatch(entityList);
    }
}

// 4 保存spu的规格参数: pms_product_attr_value

service/productAttrValueService:

void saveProductAttr(List entityList);

service/impl/ProductAttrValueServiceImpl:

public void saveProductAttr(List entityList) {
    this.saveBatch(entityList);
}

// 5 保存spu的积分信息: gulimall_sms -> sms_spu_bounds

feign/CouponFeignService:

@FeignClient("gulimall-coupon")
public interface CouponFeignService {
    @PostMapping("/coupon/spubounds/save")
    R saveSpuBounds(@RequestBody SpuBoundTo spuBoundTo);
}

coupon服务,controller/SpuBoundsController:

@PostMapping("/save")
public R save(@RequestBody SpuBoundsEntity spuBounds) {
    spuBoundsService.save(spuBounds);

    return R.ok();
}

common服务,工具类R:

public Integer getCode() {
    return (Integer) this.get("code");
}

//   6.1 sku的基本信息: pms_sku_info

service/SkuInfoService:

void saveSkuInfo(SkuInfoEntity skuInfoEntity);

service/impl/SkuInfoServiceImpl:

@Override
public void saveSkuInfo(SkuInfoEntity skuInfoEntity) {
    this.baseMapper.insert(skuInfoEntity);
}

//   6.4 sku的优惠、满减等信息: gulimall_sms -> sms_sku_ladder
//                                                 sms_sku_full_reduction
//                                                 sms_member_price

feign/CouponFeignService:

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

coupon服务,controller/SkuFullReductionController:

@PostMapping("/saveinfo")
public R saveInfo(@RequestBody SkuReductionTo skuReductionTo) {
    skuFullReductionService.saveSkuReduction(skuReductionTo);

    return R.ok();
}

coupon服务,service/SkuFullReductionService:

void saveSkuReduction(SkuReductionTo skuReductionTo);

coupon服务,service/impl/SkuFullReductionServiceImpl:

@Override
public void saveSkuReduction(SkuReductionTo skuReductionTo) {
    SkuLadderEntity skuLadderEntity = new SkuLadderEntity();
    skuLadderEntity.setSkuId(skuReductionTo.getSkuId());
    skuLadderEntity.setFullCount(skuReductionTo.getFullCount());
    skuLadderEntity.setDiscount(skuReductionTo.getDiscount());
    skuLadderEntity.setAddOther(skuReductionTo.getCountStatus());
    if (skuReductionTo.getFullCount() > 0) {
        skuLadderService.save(skuLadderEntity);
    }

    SkuFullReductionEntity skuFullReductionEntity = new SkuFullReductionEntity();
    BeanUtils.copyProperties(skuReductionTo, skuFullReductionEntity);
    if (skuFullReductionEntity.getFullPrice().compareTo(new BigDecimal("0")) == 1) {
        this.save(skuFullReductionEntity);
    }

    List memberPrice = skuReductionTo.getMemberPrice();
    List collect = memberPrice.stream().map(item -> {
        MemberPriceEntity memberPriceEntity = new MemberPriceEntity();
        memberPriceEntity.setSkuId(skuReductionTo.getSkuId());
        memberPriceEntity.setMemberLevelId(item.getId());
        memberPriceEntity.setMemberLevelName(item.getName());
        memberPriceEntity.setMemberPrice(item.getPrice());
        memberPriceEntity.setAddOther(1);
        return memberPriceEntity;
    }).filter(item -> {
        return item.getMemberPrice().compareTo(new BigDecimal("0")) == 1;
    }).collect(Collectors.toList());
    memberPriceService.saveBatch(collect);
}

7 SPU与SKU的条件查询

略。

095-099 仓库管理

1 为各个服务创建配置文件夹

在各个服务中,创建config/MybatisConfig文件:(注意修改@MapperScan的路径)

@Configuration
@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.product.dao")
public class MybatisConfig {
    // 引入分页插件
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        return paginationInterceptor;
    }
}

2 仓库维护

已自动生成增删改查代码。

在这里遇到了一个bug:因为id在数据库中的存储类型为最大长度 20位的bigint类型,当id位数很长时(简单测了一下可能是超过17位),在传到前端时会发生精度丢失。

所以在做数据库设计时,最好认真地设计一下主键,主键自增你把握不住。

这里我使用了当前时间作为主键:

SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String id = dateFormat.format(new Date(System.currentTimeMillis()));
wareInfo.setId(Long.parseLong(id));

3 商品库存

增删改查,根据仓库id或skuId查询。略。

4 商品采购

采购流程:

《谷粒商城》开发实录:070-100 商品服务-发布商品_第4张图片

100 规格维护

增删改查略。

你可能感兴趣的:(项目实战,软件开发)