百度网盘:提取码2239
在数据库gulimall-admin中执行SQL脚本文件sys_menus.sql,生成系统菜单。
将下载的前端代码复制到前端项目renren-fast-vue中,风险操作注意备份。
查看接口文档
涉及业务表:
例如,对一部手机而言,它可以有:
关于属性分组的基本增删改查略。
商品分类是三级分类的形式,下面是一套分类回写逻辑:
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:三级分类后面跟着一张空白页
原因:第三级分类的children属性是一个空集合。
解决方法:只有当children字段不为空时,才会被放入返回前端页面的json数据中。
一图流:
对数据库中冗余字段的修改,必须全部更新,或者全部不更新。
所以要在方法前加上事务注解:@Transactional
增删改查略。
这里有一个bug,请将前端文件attr-add-or-update.vue中的值类型相关代码注释掉,因为在数据库中创建表时这个字段缺失了。不想改表结构了。
增删改查略。
值得一提的是,在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;
}
}
}
关联规格参数,增删改查略。
在做这一部分之前,建议先把070-2后端代码中product/vo文件夹导入gulimall-product服务。
进入发布商品页面会出现一个PubSub is not defined的问题。
解决方法:在前端项目中
在src下的main.js中添加代码(如果在070-4导入前端代码时导入了main.js,跳过这一步)
① import PubSub from 'pubsub-js'
② Vue.prototype.PubSub = PubSub
解决了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服务。
随意添加一些会员等级。
创建接口:根据分类id查询属性及属性分组。原理是在pms_attr 属性表和pms_attr_group 属性分组表中都包含一个catelog_id字段,代码略。
随意添加一些规格参数。
在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;
}
分类与品牌的级联关系创建完成。
按照页面提示输入商品基本信息:
选择规格参数:
选择销售属性:
编辑SKU信息:
这个功能,业务繁琐但并不复杂:
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);
}
略。
在各个服务中,创建config/MybatisConfig文件:(注意修改@MapperScan的路径)
@Configuration
@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.product.dao")
public class MybatisConfig {
// 引入分页插件
@Bean
public PaginationInterceptor paginationInterceptor() {
PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
return paginationInterceptor;
}
}
已自动生成增删改查代码。
在这里遇到了一个bug:因为id在数据库中的存储类型为最大长度 20位的bigint类型,当id位数很长时(简单测了一下可能是超过17位),在传到前端时会发生精度丢失。
所以在做数据库设计时,最好认真地设计一下主键,主键自增你把握不住。
这里我使用了当前时间作为主键:
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String id = dateFormat.format(new Date(System.currentTimeMillis()));
wareInfo.setId(Long.parseLong(id));
增删改查,根据仓库id或skuId查询。略。
采购流程:
增删改查略。