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:
@Overridepublic Long[] findCatelogPath(Long catelogId) {List<Long> paths = new ArrayList<>();List<Long> parentPath = findParentPath(catelogId, paths);Collections.reverse(parentPath);return parentPath.toArray(new Long[parentPath.size()]);}// 递归private List<Long> findParentPath(Long catelogId, List<Long> paths) {paths.add(catelogId);CategoryEntity entity = this.getById(catelogId);if (entity.getParentCid() != 0) {findParentPath(entity.getParentCid(), paths);}return paths;}
bug:三级分类后面跟着一张空白页
原因:第三级分类的children属性是一个空集合。
解决方法:只有当children字段不为空时,才会被放入返回前端页面的json数据中。
075 品牌管理
1 模糊查询
一图流:
2数据库冗余字段同步
对数据库中冗余字段的修改,必须全部更新,或者全部不更新。
所以要在方法前加上事务注解:@Transactional
076-082 平台属性
1 规格参数
增删改查略。
这里有一个bug,请将前端文件attr-add-or-update.vue中的值类型相关代码注释掉,因为在数据库中创建表时这个字段缺失了。不想改表结构了。
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服务。
1PubSub is not defined
进入发布商品页面会出现一个PubSub is not defined的问题。
解决方法:在前端项目中
npminstall --save pubsub-js
在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_routeuri: lb://gulimall-productpredicates:- Path=/api/product/**filters:- RewritePath=/api/?(?<segment>.*), /$\{segment} #去掉/api 前缀- id: member_routeuri: lb://gulimall-memberpredicates:- Path=/api/member/**filters:- RewritePath=/api/?(?<segment>.*), /$\{segment}- id: coupon_routeuri: lb://gulimall-couponpredicates:- Path=/api/coupon/**filters:- RewritePath=/api/?(?<segment>.*), /$\{segment}- id: ware_routeuri: lb://gulimall-warepredicates:- Path=/api/ware/**filters:- RewritePath=/api/?(?<segment>.*), /$\{segment}- id: order_routeuri: lb://gulimall-orderpredicates:- Path=/api/order/**filters:- RewritePath=/api/?(?<segment>.*), /$\{segment}- id: third_party_routeuri: lb://gulimall-third-partypredicates:- Path=/api/thirdparty/**filters:- RewritePath=/api/thirdparty/?(?<segment>.*), /$\{segment}# 默认路由- id: admin_routeuri: lb://renren-fast #lb:负载均衡predicates:- Path=/api/** #前端项目发送的请求带有/api 前缀filters:- RewritePath=/api/?(?<segment>.*), /renren-fast/$\{segment} #将/api 前缀改写成/renren-fast
重启gulimall-gateway服务。
随意添加一些会员等级。
3 规格参数
创建接口:根据分类id查询属性及属性分组。原理是在pms_attr 属性表和pms_attr_group 属性分组表中都包含一个catelog_id字段,代码略。
随意添加一些规格参数。
4 分类与品牌的级联关系
在gulimall-product服务中:
新建vo/BrandVo:
@Datapublic class BrandVo {// 品牌idprivate Long brandId;// 品牌名称private String brandName;}
controller/CategoryBrandRelationController:
@GetMapping("/brands/list")public R relationBrandsList(@RequestParam(value = "catId") Long catId) {List<BrandEntity> vos = categoryBrandRelationService.getBrandsByCatId(catId);List<BrandVo> 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<BrandEntity> getBrandsByCatId(Long catId);
service/impl/CategoryBrandRelationServiceImpl:
@Overridepublic List<BrandEntity> getBrandsByCatId(Long catId) {List<CategoryBrandRelationEntity> catelogId = relationDao.selectList(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));List<BrandEntity> 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@Overridepublic void saveSpuInfo(SpuSaveVo vo) {// 1 保存spu基本信息: pms_spu_infoSpuInfoEntity infoEntity = new SpuInfoEntity();BeanUtils.copyProperties(vo, infoEntity);infoEntity.setCreateTime(new Date());infoEntity.setUpdateTime(new Date());this.saveBaseSpuInfo(infoEntity);// 2 保存spu的描述图片: pms_spu_info_descList<String> decript = vo.getDecript();SpuInfoDescEntity descEntity = new SpuInfoDescEntity();descEntity.setSpuId(infoEntity.getId());descEntity.setDecript(String.join(",", decript));spuInfoDescService.saveSpuInfoDesc(descEntity);// 3 保存spu的图片集: pms_spu_imagesList<String> images = vo.getImages();spuImagesService.saveImages(infoEntity.getId(), images);// 4 保存spu的规格参数: pms_product_attr_valueList<BaseAttrs> baseAttrs = vo.getBaseAttrs();List<ProductAttrValueEntity> 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_boundsBounds 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<Skus> skues = vo.getSkus();if (skues != null && skues.size() > 0) {skues.forEach(item -> {// 6.1 sku的基本信息: pms_sku_infoSkuInfoEntity 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_imagesLong 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 -> {return !StringUtils.isEmpty(entity.getImgUrl());}).collect(Collectors.toList());skuImagesService.saveBatch(imagesEntities);// 6.3 sku的销售属性信息: pms_sku_sale_attr_valueList<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());skuSaleAttrValueService.saveBatch(skuSaleAttrValueEntities);// 6.4 sku的优惠、满减等信息: gulimall_sms -> sms_sku_ladder// sms_sku_full_reduction// sms_member_priceSkuReductionTo skuReductionTo = new SkuReductionTo();BeanUtils.copyProperties(item, skuReductionTo);skuReductionTo.setSkuId(skuId);List<mon.to.MemberPrice> memberPrices = new ArrayList<>();item.getMemberPrice().forEach(memberPrice -> {mon.to.MemberPrice mp = new mon.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优惠信息失败");}}});}}@Overridepublic 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:
@Overridepublic void saveSpuInfoDesc(SpuInfoDescEntity descEntity) {this.baseMapper.insert(descEntity);}
// 3 保存spu的图片集: pms_spu_images
service/SpuImagesService:
void saveImages(Long id, List<String> images);
service/impl/SpuImagesServiceImpl:
@Overridepublic void saveImages(Long id, List<String> images) {if (images == null || images.size() == 0) {return;} else {List<SpuImagesEntity> 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<ProductAttrValueEntity> entityList);
service/impl/ProductAttrValueServiceImpl:
public void saveProductAttr(List<ProductAttrValueEntity> 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:
@Overridepublic 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:
@Overridepublic 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> memberPrice = skuReductionTo.getMemberPrice();List<MemberPriceEntity> 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);}
7SPU与SKU的条件查询
略。
095-099 仓库管理
1 为各个服务创建配置文件夹
在各个服务中,创建config/MybatisConfig文件:(注意修改@MapperScan的路径)
@Configuration@EnableTransactionManagement@MapperScan("com.atguigu.gulimall.product.dao")public class MybatisConfig {// 引入分页插件@Beanpublic 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 商品采购
采购流程:
100 规格维护
增删改查略。