当前位置: 首页 > news >正文

谷粒商城--SPU和SKU

目录

1.SPU和SKU概念

2.表的关系理解

3.导入前端代码

4.完善后端接口 

5.属性分组详情 

6.规格参数详情

7. 销售属性详情

8.分组与属性关联

9.发布商品

10.仓库服务


1.SPU和SKU概念

SPU:standard product unit(标准化产品单元):是商品信息聚合的最小单位,是一组可复用、易检索的标准化信息的集合,该集合描述了一个产品的特性。如iphone13是SPU,它是一个产品的集合

SKU:stock keeping unit(库存量单位):库存进出计量的基本单元,可以是件/盒/托盘等单位。

SKU是对于大型连锁超市DC配送中心物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品对应有唯一的SKU号。如iphone13ProMax 1T 蓝色 是SKU,

包子店中肉包子是SKU,素包子是SKU,水煎包是SKU…

 规格参数和销售属性

像这里的商品介绍,规格与包装都是属于SPU的属性。它们都属于是规格参数

image-20220806105620408

像版本,颜色等都属是SKU的销售属性

image-20220806105802342 


2.表的关系理解

属性关系-规格参数-销售属性-三级分类 关联关系 

每个三级分类下有各自的属性分组表通过id和catelogid关联,能查出每个分类下的属性分组

属性分组表和属性表通过一个属性&属性关联表进行关联,能查出每个属性分组下的属性

最终这样的关系我们可以查出每个分类的属性分组和每个属性分组对应的属性

image-20220806110205216 

通过思维导图来理解

手机是一级分类,它下面又有属性组,每个属性组又有各自的属性 

image-20220807230244792 

SPU-SKU属性表

商品属性表和属性表通过attridid进行关联,能查出每个spu的属性

sku销售属性表是为了表示spu下不同sku,比如1号spu在此表有两个sku,这两个sku有不同的销售属性,是通过和属性表关联获取

 

image-20220806110506836 

通过思维导图来理解

像网络、像素一般是固定不可选的所以是SPU属性

而内存、容量、颜色等可选的就为SKU销售属性

image-20220807231607457 


3.导入前端代码

重新执行“sys_menus.sql”,完善菜单

正常我们是在系统管理里自定义添加,步骤都是一样的,其实在前端页面添加就是把数据提交到mall_admin表中,这里我们直接把提供的sql语句导入即可!

如下结果:

image-20220806100125647 

实现点击菜单的左边,能够实现在右边展示数据

image-20220806112654338 

这个页面就是三级分类和一个表格显示在一块对吧,属于是父子组件交互

前端不具体写了,我们直接导入代码,效果如下:


4.完善后端接口 

什么是开发接口

开发接口就是开发Controller、service、dao

在线接口文档如下

03、获取分类属性分组 - 谷粒商城谷粒商城 - 03、获取分类属性分组,http GET /product/attrgroup/list/{catelogId},,技术团队的文档管理平台,接口文档工具,支持在线接口调试,一键生成API文档,适合编写接口文档、产品文档、使用手册icon-default.png?t=N176https://easydoc.net/s/78237135/ZUqEdvA4/OXTgKobR
别人告诉你需要什么功能,需要返回什么样的数据,你就通过接口的形式把他们呢实现出来即可!

以后工作了也是这种形式,主要是开发接口为多,前端其实不用写太多,能看懂即可


5.属性分组详情 

显示属性分组image-20220810220705409

controller

    @RequestMapping("/list/{catelogId}")public R list(@RequestParam Map<String, Object> params,@PathVariable("catelogId") Long catelogId){
//        PageUtils page = attrGroupService.queryPage(params);PageUtils page = attrGroupService.queryPage(params, catelogId);return R.ok().put("page", page);}

 service

这里注意,前端有两个查询按钮

查询和查询全部

这两个都要有模糊查询的功能!

PageUtils queryPage(Map<String, Object> params, Long catelogId);@Override
public PageUtils queryPage(Map<String, Object> params, Long catelogId) {//多条件查询String key = (String) params.get("key");QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<>();if (!StringUtils.isEmpty(key)) {wrapper.and((obj) -> {obj.eq("attr_group_id",key).or().like("attr_group_name",key);});}if (catelogId == 0) {//如果是默认的是查全部的一级分类IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),wrapper);return new PageUtils(page);} else {wrapper.eq("catelog_id", catelogId);IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);return new PageUtils(page);}
}

 image-20220811091620116

属性分组回显 

这一部分主要是做属性分组的数据回显的

image-20220810220631503 

controller

/*** 信息*/
@RequestMapping("/info/{attrId}")
public R info(@PathVariable("attrId") Long attrId){AttrEntity attr = attrService.getById(attrId);Long catelogId = attr.getCatelogId();Long[] path = categoryService.findCatelogPath(catelogId);attr.setCatelogPath(path);return R.ok().put("attr", attr);
}

 service

获取分类路径id

通过递归操作完成

过程

给一个分类id,不断的查它的父类id直到查不到为止,最后把查询到的id到放到一个集合里

怎样写好递归?

  1. 确定参数值和返回值
  2. 确定终止条件
  3. 递归逻辑

三者缺一不可!!!

//找到catelogId的完整路径:[父/子/孙]
@Override
public Long[] findCatelogPath(Long catelogId) {ArrayList<Long> list = new ArrayList<>();List<Long> parentPath = findParentPath(catelogId, list);//1.确定递归参数和返回值Collections.reverse(parentPath);return (Long[]) list.toArray(new Long[parentPath.size()]);
}private List<Long> findParentPath(Long catelogId,ArrayList<Long> list){//3.递归逻辑list.add(catelogId);CategoryEntity entity = this.getById(catelogId);if (entity.getParentCid()!=0){//2.递归终止条件findParentPath(entity.getParentCid(),list);}return list;
}

 测试

返回属性的父路径id

image-20220811092606249 


6.规格参数详情

接口如下

image-20220810220725125 

什么是规格参数

image-20220808231624265 

保存规格参数 

/*** 保存*/
@RequestMapping("/save")
public R save(@RequestBody AttrVo vo){attrService.saveAttr(vo);return R.ok();
}

service

这里注意,因为添加规格参数的时候会有选择属性组,因为属性组和属性是通过关联关系表连接的所以要有级联操作。

在往pms_attr表插入数据的时候,pms_attr_group_relation也要插入

小bug:这里有个注意点,当添加规格参数的时候如果没有指定规格参数所属分组,那么就不应该在关联表中保存关联关系!!!

@Override
public void saveAttr(AttrVo attr) {AttrEntity attrEntity = new AttrEntity();//1.将前端接收数据的对象vo赋值给attrEntity对象,从而更新数据库BeanUtils.copyProperties(attr, attrEntity);this.save(attrEntity);if (attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() && attr.getAttrGroupId() != null) {//2.保存关联关系//因为属性组和属性是通过关联关系表连接的AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();relationEntity.setAttrGroupId(attr.getAttrGroupId());relationEntity.setAttrId(attrEntity.getAttrId());relationService.save(relationEntity);}}

 显示规格参数

/*** 显示规格参数*/
@GetMapping("/base/list/{catelogId}")
public R baseAttrList(@RequestParam Map<String, Object> params,@PathVariable("catelogId") Integer catelogId) {PageUtils page = attrService.queryBaseAttrPage(params, catelogId);return R.ok().put("page", page);
}

service

//分页查询规格参数
@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, Integer catelogId) {QueryWrapper<AttrEntity> wrapper = new QueryWrapper<>();if (catelogId != 0) {//如果不是一级分类,那么查询的时候加上where catelog_id = ?wrapper.eq("catelog_id", catelogId);}//多条件模糊查询//搜索框里的key不但可以对catelog_id进行模糊查询,对attr_name也模糊查询String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.eq("attr_id", key).or().like("attr_name", key);}//多条件分页查询IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),wrapper);PageUtils pageUtils = new PageUtils(page);return pageUtils;
}

测试

image-20220810205809739

这些属性的分类和所属分组怎么查呢

规格参数表(pms_attr)中,有所属分类的信息,可以直接调用分类的service进行查询

那分组信息怎么查询呢?规格参数表中没有所属分类相关的信息…

这里我们就要借助第三张表,属性和分组表(pms_attr_attrgroup_relation)进行查询

通过规格参数表(pms_attr)获得attr_id,之后在调用属性和分组表的service获得属性和分组表的实体类,从而获得该属性的分组

下面通过stream流的方式,通过map给list集合中的每一项做映射给新实体类(AttrRespVo)赋值,最后返回AttrRespVo

小bug:这里显示规格参数的时候,会显示规格。参数对应的分组、分类,那么如果它们查出对象分组id或分类id为空那就不设置名字if (attrId != null && attrId.getAttrGroupId() != null) {…}

 

//分页查询规格参数
@Override
public PageUtils queryBaseAttrPage(Map<String, Object> params, String type, Integer catelogId) {QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("attr_type", "base".equalsIgnoreCase(type) ? ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() : ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());if (catelogId != 0) {//如果不是一级分类,那么查询的时候加上where catelog_id = ?//IgnoreCase忽略大小写wrapper.eq("catelog_id", catelogId);}//多条件模糊查询//搜索框里的key不但可以对catelog_id进行模糊查询,对attr_name也模糊查询String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.eq("attr_id", key).or().like("attr_name", key);}//多条件分页查询IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),wrapper);PageUtils pageUtils = new PageUtils(page);List<AttrEntity> list = page.getRecords();//        .map()这个方法是对被筛选过后的流进行映射,一般是对属性进行赋值。List<AttrRespVo> resultList = list.stream().map(item -> {AttrRespVo attrRespvo = new AttrRespVo();BeanUtils.copyProperties(item, attrRespvo);//设置分类和分组的名字if ("base".equalsIgnoreCase(type)) {AttrAttrgroupRelationEntity attrId = relationService.getOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", item.getAttrId()));if (attrId != null && attrId.getAttrGroupId() != null) {//attrgroupRelationEntity.getAttrGroupId()也可以,这里可以直接放进去对象AttrGroupEntity attrGroupEntity = attrGroupService.getById(attrId.getAttrGroupId());attrRespvo.setGroupName(attrGroupEntity.getAttrGroupName());}}CategoryEntity categoryEntity = categoryService.getById(item.getCatelogId());if (categoryEntity != null) {attrRespvo.setCatelogName(categoryEntity.getName());}//返回最后的封装结果return attrRespvo;}).collect(Collectors.toList());//返回的结果是一个集合pageUtils.setList(resultList);//        返回分页后的集合对象return pageUtils;
}

AttrRespVo

@Data
public class AttrRespVo extends AttrVo {private String catelogName;private String  groupName;
}

测试

image-20220811084206902

规格参数回显 

可以看出所属分类和分组都是由这条请求查询的,那么我们改这个接口功能就行

相当于在原来查询基础上返回分类路径信息分组信息

image-20220810221107404 

controller

/*** 信息*/
@RequestMapping("/info/{attrId}")
public R info(@PathVariable("attrId") Long attrId) {AttrRespVo respVo = attrService.getAttrInfo(attrId);return R.ok().put("attr", respVo);
}

 service

@Override
public AttrRespVo getAttrInfo(Long attrId) {AttrRespVo respVo = new AttrRespVo();AttrEntity attrEntity = this.getById(attrId);BeanUtils.copyProperties(attrEntity, respVo);/*** 设置分组信息*/AttrAttrgroupRelationEntity attrgroupRelationEntity = relationService.getOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));if (attrgroupRelationEntity != null){respVo.setAttrGroupId(attrgroupRelationEntity.getAttrGroupId());Long attrGroupId = attrgroupRelationEntity.getAttrGroupId();AttrGroupEntity attrGroupEntity = attrGroupService.getById(attrGroupId);if (attrGroupEntity != null) {respVo.setGroupName(attrGroupEntity.getAttrGroupName());}}/*** 设置分类信息*/Long catelogId = attrEntity.getCatelogId();//有了分类的完整路径,接下来就设置分类名字Long[] catelogPath = categoryService.findCatelogPath(catelogId);respVo.setCatelogPath(catelogPath);//获得分类名字CategoryEntity categoryEntity = categoryService.getById(catelogId);if (categoryEntity != null) {respVo.setCatelogName(categoryEntity.getName());}return respVo;
}

测试

image-20220810232827723

修改Or增加 

提交修改分类和分组是无效的?

更改用的还是默认的update方法,所以我们改update接口!

image-20220810232652674 

controller

/*** 修改*/
@RequestMapping("/update")
public R update(@RequestBody AttrVo attr) {attrService.updateAttr(attr);return R.ok();
}

 service

这里做了优化,对于规格参数中没有所属分组的,如果指定了不在是修改而是添加!

怎么判断规格参数有没有所属分组呢?

拿attr_id去pms_attr_attrgroup_relation表中查询,如果改attr_id存在与该表,那就修改关联关系

如果没有数据,那么就在此表添加数据!

@Transactional
@Override
public void updateAttr(AttrVo attr) {AttrEntity attrEntity = new AttrEntity();BeanUtils.copyProperties(attr, attrEntity);this.updateById(attrEntity);//修改分组关联AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());attrAttrgroupRelationEntity.setAttrId(attr.getAttrId());//统计attr_id的关联属性,如果没有初始分组,则进行添加操作;有则进行修改操作Integer count = relation.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));if (count > 0) {relation.update(attrAttrgroupRelationEntity, new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));} else {relation.insert(attrAttrgroupRelationEntity);}
}

 spu规格维护

出现400页面,在数据库添加

INSERT INTO sys_menu (menu_id, parent_id, name, url, perms, type, icon, order_num) VALUES (76, 37, '规格维护', 'product/attrupdate', '', 2, 'log', 0);更新index.js,哪里更新?找老师的源码

 controller

@PostMapping("/update/{spuId}")
public R updateSpuAttr(@PathVariable("spuId") Long spuId,@RequestBody List<ProductAttrValueEntity> entities){productAttrValueService.updateSpuAttr(spuId,entities);return R.ok();
}

impl

这里的修改其实是先把原来的spu_id下的属性都删除掉

之后在把前端传来的属性集合进行批量保存

@Transactional(rollbackFor = Exception.class)
@Override
public void updateSpuAttr(Long spuId, List<ProductAttrValueEntity> entities) {//1、删除spuId之前对应的所有属性this.baseMapper.delete(new QueryWrapper<ProductAttrValueEntity>().eq("spu_id",spuId));//2、添加商品规格信息List<ProductAttrValueEntity> collect = entities.stream().map(item -> {item.setSpuId(spuId);return item;}).collect(Collectors.toList());//批量新增this.saveBatch(collect);
}

7. 销售属性详情

显示销售属性

如图http://localhost:88/api/product/attr/sale/list/0?t=1660181297434&page=1&limit=10&key=这个接口有问题!

所以我们就去后端改这个接口即可!

image-20220811092412567 

controller

规格参数和销售参数的区别在于type的值,type为 1是规格参数type为0是销售参数

这里采用一个方法当两个来用!

image-20220811094628873 

@GetMapping("/{attrType}/list/{catelogId}")
public R baseAttrList(@RequestParam Map<String, Object> params,@PathVariable("attrType") String type,@PathVariable("catelogId") Integer catelogId) {PageUtils page = attrService.queryBaseAttrPage(params, type, catelogId);return R.ok().put("page", page);
}

 service

在原来对规格参数的基础上加了限制条件,如果是规格参数那就是WHERE attr_type = 1,否则就是WHERE attr_type = 0;

下面的逻辑和查询规格参数一致,都要模糊查询

这里为了使代码更通用,1和0的值我们写一个常量来控制,如过后期换值了我们直接更改常量的值即可

ProductConstant

package com.xxh.common.constant;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;}}
}

在原来对规格参数的基础上加了限制条件,如果是规格参数那就是WHERE attr_type = 1,否则就是WHERE attr_type = 0;

@Overridepublic PageUtils queryBaseAttrPage(Map<String, Object> params, String type, Integer catelogId) {QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("attr_type", "base".equalsIgnoreCase(type) ? ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() : ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());if (catelogId != 0) {//如果不是一级分类,那么查询的时候加上where catelog_id = ?//IgnoreCase忽略大小写wrapper.eq("catelog_id", catelogId);}//多条件模糊查询//搜索框里的key不但可以对catelog_id进行模糊查询,对attr_name也模糊查询String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.eq("attr_id", key).or().like("attr_name", key);}//多条件分页查询IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),wrapper);PageUtils pageUtils = new PageUtils(page);List<AttrEntity> list = page.getRecords();
//        .map()这个方法是对被筛选过后的流进行映射,一般是对属性进行赋值。List<AttrRespVo> resultList = list.stream().map(item -> {AttrRespVo attrRespvo = new AttrRespVo();BeanUtils.copyProperties(item, attrRespvo);AttrAttrgroupRelationEntity attrgroupRelationEntity = relationService.getOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", item.getAttrId()));if (attrgroupRelationEntity != null) {//attrgroupRelationEntity.getAttrGroupId()也可以,这里可以直接放进去对象AttrGroupEntity attrGroupEntity = attrGroupService.getById(attrgroupRelationEntity);attrRespvo.setGroupName(attrGroupEntity.getAttrGroupName());}CategoryEntity categoryEntity = categoryService.getById(item.getCatelogId());if (categoryEntity != null) {attrRespvo.setCatelogName(categoryEntity.getName());}//返回最后的封装结果return attrRespvo;}).collect(Collectors.toList());//返回的结果是一个集合pageUtils.setList(resultList);//        返回分页后的集合对象return pageUtils;}

 销售属性回显

可以看到,销售属性回显是不需要所属分组的

但是销售属性规格参数用的是同一个回显方法,我们也进行更改,只有是规格参数的时候才进行分组回显

image-20220811104643952 

在原分组回显的逻辑上加上判断,后面逻辑不变 

if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {...
}

 image-20220811104920477

修改销售属性 

销售属性规格参数用的是同一个修改方法,销售属性进行修改时,会对关联表进行一个级联更新,但销售属性不需要

所以也在对关联表级联更新的时候进行判断,只有销售属性修改的时候才进行级联更新!

if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {...
}

 image-20220811105227416

保存销售属性 

销售属性规格参数用的是同一个保存方法,销售属性进行保存时,会对关联表进行一个级联保存,但销售属性不需要

所以也在对关联表级联保存的时候进行判断,只有销售属性保存的时候才进行级联保存!

if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {...
}

 image-20220811105415725


8.分组与属性关联

显示属性 

这里其实就是一个分布查询,流程如下:

  1. 点击分组属性的时候获取到分组id,
  2. 拿分组id去关联表查分组id对应的attr_id
  3. attr_id去pms_attr表中获取属性

image-20220808231237643 

controller

/*** 3.获取属性分组的关联的所有属性*/
@RequestMapping("/{attrgroupId}/attr/relation")
public R attrRelation(@PathVariable("attrgroupId") Long attrgroupId) {List<AttrEntity> entities = attrService.getRelationAttr(attrgroupId);return R.ok().put("data", entities);
}

 service

@Override
public List<AttrEntity> getRelationAttr(Long attrgroupId) {//分布查询,第一步去关联表中查出所有的组和属性idList<AttrAttrgroupRelationEntity> entities = relationService.list(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id",attrgroupId));//第二收集属性idList<Long> attrIds = entities.stream().map((attr) -> {return attr.getAttrId();}).collect(Collectors.toList());List<AttrEntity> list = this.listByIds(attrIds);return list;
}

测试

image-20220811124827549

移除属性 

这里为了方便,我们直接写一个批量删除的接口

controller

  1. /product/attrgroup/attr/relation/delete
  2. post请求会带来json数据,要封装成自定义对象vos需要@RequestBody注解
  3. 意思就是将请求体中的数据封装成vos
/*** 4.移除属性分组和属性的关系*/
@PostMapping("/attr/relation/delete")
public R deleteRelation(@RequestBody AttrGroupRelationVo[] vos) {attrService.deleteRelation(vos);return R.ok();
}

 service

@Override
public void deleteRelation(AttrGroupRelationVo[] vos) {List<AttrAttrgroupRelationEntity> entities = Arrays.asList(vos).stream().map((item) -> {AttrAttrgroupRelationEntity entity = new AttrAttrgroupRelationEntity();BeanUtils.copyProperties(item, entity);return entity;}).collect(Collectors.toList());relation.deleteBatchRelation(entities);
}

mapper

void deleteBatchRelation(@Param("entities") List<AttrAttrgroupRelationEntity> entities);<delete id="deleteBatchRelation">DELETE FROM `pms_attr_attrgroup_relation` where<foreach collection="entities" item="item" separator="OR">(attr_id = #{item.attrId} AND attr_group_id = #{item.attrGroupId})</foreach></delete>

查询分组未关联的属性

image-20220811172114595

controller

/*** 5.获取属性分组没有关联的所有属性* /product/attrgroup/{attrgroupId}/noattr/relation*/
@RequestMapping("/{attrgroupId}/noattr/relation")
public R attrNoRelation(@RequestParam Map<String, Object> params,@PathVariable("attrgroupId") Long attrgroupId) {PageUtils page = attrService.getNoRelationAttr(params,attrgroupId);return R.ok().put("page", page);
}

 service

认真看注释,认真理解,还是很绕的

查询分组未关联的数据三步!

  1. 获得当前分类下的所有分组
  2. 获得这些分组下所有已添加的属性
  3. 添加新属性时移除这些已添加的属性
@Override
public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrgroupId) {/***  1.当前分组只能关联自己所属的分类里面的所有属性*/AttrGroupEntity attrGroupEntity = attrGroupService.getById(attrgroupId);Long catelogId = attrGroupEntity.getCatelogId();/***  2 .当前分组只能引用别的分组没有引用的属性*  2.1 当前分类下的所有分组*  2.2 这些分组关联的属性*  2.3 从当前分类的所有属性中移除这些属性*//*** 2.1 当前分类下的所有分组。收集到他们的组id*/List<AttrGroupEntity> group = attrGroupService.list(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));List<Long> collectGroupIds = group.stream().map((item) -> {return item.getAttrGroupId();}).collect(Collectors.toList());/***  2.2 收集到分组的所有属性*  (1)拿着上一步收集到的组id到关系表中查找关系表实体类对象,*  (2)通过关系表实体类对象获得所有分组下的所有属性id*/List<AttrAttrgroupRelationEntity> groupId = relationService.list(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", collectGroupIds));List<Long> attrIds = groupId.stream().map((item) -> {return item.getAttrId();}).collect(Collectors.toList());/*** 2.3 从当前分类的所有属性中移除这些属性并筛选出基本属性(where attr_type = 1)*/QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("catelog_id", catelogId).eq("attr_type",ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());//如果其他分组也没关联属性,那么就不加这个条件if (attrIds != null && attrIds.size() > 0){wrapper.notIn("attr_id", attrIds);}/*** 分页多条件查询* where (`attr_id` = ? or `attr_name` like ?)*/String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.and((w) -> {w.eq("attr_id", key).or().like("attr_name", key);});}/*** page方法需要两个参数* 1.IPage对象(通过工具类Query获取并通过.getPage(params)封装页面传来分页参数)* 2.wrapper(自己生成)*/IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), wrapper);PageUtils pageUtils = new PageUtils(page);return pageUtils;
}

 注意非空判断

image-20220811173223505

测试

image-20220811183248352 

给销售属性绑定分组,把9号属性绑定给1号分组 

image-20220811183459861 

查询分组未关联的属性 

image-20220811183601207

image-20220811183612731 

添加属性关联 

常规的调用,注意点是saveBatch传的参数是数据对应的实体类

我们想传其他vo时,需要对这个方法进行一个重写

最后也是通过把vo的值赋给对应实体类,在调用相应批量保存

controller

/*** 6.添加属性与分组关联关系* /product/attrgroup/attr/relation*/
@PostMapping("/attr/relation")
public R addRelation(@RequestBody List<AttrGroupRelationVo> vos) {relationService.saveBatch(vos);return R.ok();
}

 service

@Override
public void saveBatch(List<AttrGroupRelationVo> vos) {List<AttrAttrgroupRelationEntity> collect = vos.stream().map((item) -> {AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();BeanUtils.copyProperties(item, relationEntity);return relationEntity;}).collect(Collectors.toList());this.saveBatch(collect);
}

9.发布商品

调试会员等级接口

启动会员微服务,添加网关,添加前端页面…

添加如下会员:

image-20220812111110133 

获取分类关联的品牌 

/*** 1.获取分类关联的品牌* /product/categorybrandrelation/brands/list*/
@GetMapping("/brands/list")
public R relationBrandList(@RequestParam(value = "catId", required = true) Long catId) {List<BrandEntity> vos = categoryBrandRelationService.getBrandsByCatId(catId);//品牌对象集合在进行筛选,赋予品牌对象id和name,返回封装的vo给前端List<BrandVo> collect = 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",collect);
}

service

@Override
public List<BrandEntity> getBrandsByCatId(Long catId) {//获得CategoryBrandRelationEntity集合对象List<CategoryBrandRelationEntity> catelogId = relationDao.selectList(new QueryWrapper<CategoryBrandRelationEntity>().eq("catelog_id", catId));//获得所有集合对象中brandid,通过brandService查询所有品牌,封装成品牌对象集合List<BrandEntity> collect = catelogId.stream().map(item -> {Long brandId = item.getBrandId();BrandEntity entity = brandService.getById(brandId);return entity;}).collect(Collectors.toList());//返回品牌对象集合return collect;
}

测试

开发规范

  1. Controller:处理请求,接受和校验数据
  2. Service接受controller传来的数据,进行业务处理
  3. Controller接受service处理完的数据,封装页面指定的vo

 image-20220812115152211

获取分类下所有分组&关联属性 

也就是说当我们选择手机分类时,那就查出手机相关的分组信息,并查出每个分组相应属性信息

image-20220812180531794 

@Data
public class AttrGroupWithAttrsVo {/*** 分组id*/@TableIdprivate Long attrGroupId;/*** 组名*/private String attrGroupName;/*** 排序*/private Integer sort;/*** 描述*/private String descript;/*** 组图标*/private String icon;/*** 所属分类id*/private Long catelogId;private List<AttrEntity> attrs;
}

 controller

/*** 7.获取分类下所有分组&关联属性* /product/attrgroup/{catelogId}/withattr*/
@GetMapping("/{catelogId}/withattr")
public R getAttrGroupWithAttrs(@PathVariable("catelogId") Long catelogId) {List<AttrGroupWithAttrsVo> vos = attrGroupService.getAttrGroupWithAttrsByCatelogId(catelogId);return R.ok().put("data",vos);
}

service

vo的重要性:

vo(value object)当相应数据需要自定义时,用vo是最好的选择,不需要对实体类字段进行修改

 image-20220812181322056

/*** 获取分类下的所有分组及属性* @param catelogId* @return*/@Overridepublic List<AttrGroupWithAttrsVo> getAttrGroupWithAttrsByCatelogId(Long catelogId) {/** 1.获取分类下的所有分组,封装成集合*  分类和组的关系在pms_group表中,所以(where catelog_id = ?)即可查出分类对应的组*  由于这是mp,它会得出所有的这种关系,并把结果封装成集合*/List<AttrGroupEntity> list = this.list(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));/** 2.获得分组下的属性*  要第三张关联表,直接调用关联表的service即查询分组对应的属性id*  获得属性id在去调用属性表的service即可查询属性名*  以上两步前面已经写好逻辑了直接调用即可attrService.getRelationAttr(groupId)*/List<AttrGroupWithAttrsVo> collect = list.stream().map((item) -> {AttrGroupWithAttrsVo attrGroupWithAttrsVo = new AttrGroupWithAttrsVo();BeanUtils.copyProperties(item, attrGroupWithAttrsVo);List<AttrEntity> attrs = attrService.getRelationAttr(attrGroupWithAttrsVo.getAttrGroupId());if (attrs != null) {attrGroupWithAttrsVo.setAttrs(attrs);}return attrGroupWithAttrsVo;}).filter((attrvo) -> {return attrvo.getAttrs() != null && attrvo.getAttrs().size() > 0;}).collect(Collectors.toList());return collect;}

 测试

image-20220812180358798

商品新增vo抽取 

设置完属性,点击保存之后取消保存,复制控制台输出

image-20220812210242531 

在线JSON字符串转Java实体类(JavaBean、Entity)-BeJSON.com 

直接解析json数据封装成实体类

这里我简单截取一个主要的Vo

此Vo包括每个步骤所携带的数据,有的是单个字段有的是一个集合

逻辑不难,难点是要理清逻辑,注意细节!

@Data
public class SpuSaveVo {@NotEmpty(groups = {AddGroup.class})private String spuName;private String spuDescription;@NotEmpty(groups = {AddGroup.class})private Long catalogId;@NotEmpty(groups = {AddGroup.class})private Long brandId;private double weight;private int publishStatus;private List<String> decript;private List<String> images;private Bounds bounds;@NotEmpty(groups = {AddGroup.class})private List<BaseAttrs> baseAttrs;@NotEmpty(groups = {AddGroup.class})private List<Skus> skus;}

 商品新增业务流程分析

逻辑很简单那,就是把数据保存到多张表

因为这个Vo收集的数据很多,包括每个步骤你所选择的数据

保存spu基本信息 pms_spu_info

因为所有传来的信息都在vo里,所以我们把信息拷贝到对应的实体类中,如果vo没有的那就可以自己赋值

表结构如下:

image-20220813192926303 

这里的infoEntity.setCreateTime(new Date());infoEntity.setUpdateTime(new Date());是因为前端传入的是没有这两个字段的,我们自己赋值即可 

SpuInfoEntity infoEntity = new SpuInfoEntity();
BeanUtils.copyProperties(vo, infoEntity);
infoEntity.setCreateTime(new Date());
infoEntity.setUpdateTime(new Date());
this.saveBaseInfo(infoEntity);

 保存spu的描述图片 pms_spu_info_desc

保存哪个数据到哪个表,就注入那个service

String.join()的作用是把集合中的元素通过","分割形成一个一个的字符串

List<String> decript = vo.getDecript();
SpuInfoDescEntity descEntity = new SpuInfoDescEntity();
descEntity.setSpuId(infoEntity.getId());
descEntity.setDecript(String.join(",", decript));
spuInfoDescService.saveSpuInfoDesc(descEntity);

 保存spu的图片集 pms_spu_images

从vo中获取所有图片集合
调用图片service进行保存,保存只需要两个点
图片id和url地址,传入对象即可

List<String> images = vo.getImages();
imagesService.saveImages(infoEntity.getId(), images);

 保存spu的规格参数 pms_product_attr_value

从vo中获取所有规格参数集合
对规格参数集合进行遍历,设置每项的属性

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);

 保存spu的积分信息 mall_sms -> sms_spu_bounds

Bounds bounds = vo.getBounds();
SpuBoundTo spuBoundTo = new SpuBoundTo();
BeanUtils.copyProperties(bounds, spuBoundTo);
spuBoundTo.setSpuId(infoEntity.getId());
R r0 = couponFeignService.saveSpuBounds(spuBoundTo);
if (r0.getCode() != 0) {log.error("远程保存spu积分信息异常");
}
couponFeignService.saveSpuBounds(spuBoundTo);

保存当前spu对应的所有sku信息

//6.1sku的基本信息;pms_sku_info
List<Skus> skus = vo.getSkus();
if (skus != null && skus.size() > 0) {skus.forEach(item -> {String defalutImg = "";for (Images image : item.getImages()) {if (image.getDefaultImg() == 1) {defalutImg = image.getImgUrl();}}SkuInfoEntity skuInfoEntity = new SkuInfoEntity();BeanUtils.copyProperties(item, skuInfoEntity);//添加vo中没有的信息skuInfoEntity.setBrandId(infoEntity.getBrandId());skuInfoEntity.setCatalogId(infoEntity.getCatalogId());skuInfoEntity.setSaleCount(0L);skuInfoEntity.setSpuId(infoEntity.getId());skuInfoEntity.setSkuDefaultImg(defalutImg);skuInfoService.saveSkuInfo(skuInfoEntity);//6.2sku图片信息;pms_sku_images//没有图片路径的无需保存Long skuId = skuInfoEntity.getSkuId();List<SkuImagesEntity> imageEntities = 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(imageEntities);//6.3sku的销售属性;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.4sku的优惠满减信息(跨服务);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("远程保存spu积分信息异常");}}});
}

测试

检索功能 

也就是多条件分页查询,很常见的功能!spu检索

/*** 列表*/
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){PageUtils page = spuInfoService.queryPageByCondition(params);return R.ok().put("page", page);
}

service

@Override
public PageUtils queryPageByCondition(Map<String, Object> params) {QueryWrapper<SpuInfoEntity> queryWrapper = new QueryWrapper<>();String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {//等价sql: status=1 and (id=1 or spu_name like xxx)queryWrapper.and((w) -> {w.eq("id", key).or().like("spu_name", key);});}String status = (String) params.get("status");if (!StringUtils.isEmpty(status)) {queryWrapper.eq("publish_status", status);}String brandId = (String) params.get("brandId");if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) {queryWrapper.eq("brand_id", brandId);}String catelogId = (String) params.get("catelogId");if (!StringUtils.isEmpty(catelogId) && !"0".equalsIgnoreCase(catelogId)) {queryWrapper.eq("catalog_id", catelogId);}IPage<SpuInfoEntity> page = this.page(new Query<SpuInfoEntity>().getPage(params),queryWrapper);return new PageUtils(page);
}

 sku检索

/*** 列表*/
@RequestMapping("/list")
public R list(@RequestParam Map<String, Object> params){PageUtils page = skuInfoService.queryPageByParams(params);return R.ok().put("page", page);
}

 service

@Override
public PageUtils queryPageByParams(Map<String, Object> params) {QueryWrapper<SkuInfoEntity> queryWrapper = new QueryWrapper<>();String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {queryWrapper.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)) {queryWrapper.eq("catalog_id", catelogId);}String brandId = (String) params.get("brandId");if (!StringUtils.isEmpty(brandId) && !"0".equalsIgnoreCase(brandId)) {queryWrapper.eq("brand_id", brandId);}String max = (String) params.get("max");if (!StringUtils.isEmpty(max)) {try {BigDecimal bigDecimal = new BigDecimal(max);if (bigDecimal.compareTo(new BigDecimal("0")) == 1) {queryWrapper.le("price", max);}} catch (Exception e) {}}String min = (String) params.get("min");if (!StringUtils.isEmpty(min)) {queryWrapper.ge("price", min);}IPage<SkuInfoEntity> page = this.page(new Query<SkuInfoEntity>().getPage(params),queryWrapper);return new PageUtils((page));
}

10.仓库服务

整合ware服务&获取仓库列表

  1. 加入微服务注册中心
  2. 加入网关

获取仓库列表就是对仓库表的简单查询,逆向生成代码以帮我们生成好,只要配置好网关就可以直接显示

image-20220906101248116 

我们只要记住,反是单表操作的逆向生成以帮我们生成好了,我们能拿来直接用,就像增加仓库、删除、修改都是可以直接用的 

多条件分页查询 

@Override
public PageUtils queryPage(Map<String, Object> params) {QueryWrapper<WareInfoEntity> queryWrapper = new QueryWrapper<>();String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {queryWrapper.eq("id", key).or().like("name", key).or().like("address", key).or().like("areacode", key);}IPage<WareInfoEntity> page = this.page(new Query<WareInfoEntity>().getPage(params),queryWrapper);return new PageUtils(page);
}

多条件查询都是一样的套路,获得你搜索的key,然后拿这个key去模糊匹配多个字段

比如这里拿你输入的key会在name、address、areacode做模糊查询,条件直接通过or来拼接

查询库存

查询库存也是单表操作,CRUD都帮我们做好了,我们就在分页的基础上加上多条件查询即可 

//多条件分页查询
@Override
public PageUtils queryPage(Map<String, Object> params) {QueryWrapper<WareSkuEntity> queryWrapper = new QueryWrapper<>();String skuId = (String) params.get("skuId");if (!StringUtils.isEmpty(skuId)) {queryWrapper.eq("sku_id", skuId);}String wareId = (String) params.get("wareId");if (!StringUtils.isEmpty(wareId)) {queryWrapper.eq("ware_id", wareId);}IPage<WareSkuEntity> page = this.page(new Query<WareSkuEntity>().getPage(params),queryWrapper);return new PageUtils(page);
}

 创建采购需求

同上都是单表操作,我们只需要做采购需求的多条件分页查询

@Override
public PageUtils queryPage(Map<String, Object> params) {QueryWrapper<PurchaseDetailEntity> queryWrapper = new QueryWrapper<PurchaseDetailEntity>();String key = (String)params.get("key");if(!StringUtils.isEmpty(key)){queryWrapper.and(w->{w.eq("purchase_id",key).or().eq("sku_id",key);});}String status = (String)params.get("status");if(!StringUtils.isEmpty(status)) {queryWrapper.eq("status",status);}String wareId = (String)params.get("wareId");if(!StringUtils.isEmpty(wareId)) {queryWrapper.eq("ware_id",wareId);}IPage<PurchaseDetailEntity> page = this.page(new Query<PurchaseDetailEntity>().getPage(params),queryWrapper);return new PageUtils(page);
}

 合并采购需求

image-20220906105205032

创建采购单

image-20220906105356726 

合并请求接口

这里有两种情况如下:

  • 如果没有选中采购单,那么会自动创建采购单进行合并
  • 有的话,就用采购单id

 controller

/*** 合并采购单*/
@PostMapping("/merge")
public R merge(@RequestBody MergeVo mergeVo) {boolean flag = purchaseService.mergePurchase(mergeVo);if(flag){return R.ok();}else {return R.error().put("msg","请选择新建或已分配的采购需求");}
}VO如下:@Data
public class MergeVo {private Long purchaseId;private List<Long> items;
}

impl

实际上就是创建完采购需求对象和采购单对象后,点击合并,这两个对象信息会发生变化,整体就是做这些操作

具体的看注释,这里还用到了一些枚举类的写法,通过枚举类获得状态信息,了解即可,这里就不写了,可以去看老师的源码

@Transactional
@Override
public boolean mergePurchase(MergeVo mergeVo) {//一、获取Vo中的信息//如果指定了采购单,那就获取采购单的idLong purchaseId = mergeVo.getPurchaseId();//获得采购需求的idList<Long> items = mergeVo.getItems();//二、过滤采购需求//对采购需求id进行过滤,如果采购需求处于新建或者已分配的收集成新的集合//这样做的目的是为了进行筛选,如果你选中正在采购的是不会被合并的List<Long> collect = items.stream().filter(i -> {//通过采购需求的id获取采购需求实体类PurchaseDetailEntity purchaseDetailEntity = purchaseDetailService.getById(i);if (purchaseDetailEntity.getStatus() == WareConstant.PurchaseDetailStatusEnum.CREATED.getCode()|| purchaseDetailEntity.getStatus() == WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode()) {return true;} else {return false;}}).collect(Collectors.toList());//三、没有指定采购单逻辑和指定了的逻辑if (collect != null && collect.size() > 0) {//3.1如果没有指定采购单,那就自动创建一个if (purchaseId == null) {PurchaseEntity purchaseEntity = new PurchaseEntity();//如果是新创建的采购单,创建时间更新时间,状态都是没有默认值的所以这默认值我们自己来赋值purchaseEntity.setCreateTime(new Date());purchaseEntity.setUpdateTime(new Date());//这里设置采购单的状态采用的是枚举类的形式获取purchaseEntity.setStatus(WareConstant.PurchaseStatusEnum.CREATED.getCode());this.save(purchaseEntity);//获得自动创建的采购单idpurchaseId = purchaseEntity.getId();}/** 3.2指定采购单了,逻辑如下* 1.采购单id为Vo中获取的指定id* 2.设置所有的采购需求对象并收集成对象*/Long finalPurchaseId = purchaseId;List<PurchaseDetailEntity> collect1 = collect.stream().map(i -> {//获取所有的采购需求对象//更新采购需求的状态,一共需要该两个点,一个是采购状态,一个是采购单id。设置采购需求的id是为了区分是哪一个进行了更改PurchaseDetailEntity purchaseDetailEntity = purchaseDetailService.getById(i);purchaseDetailEntity.setPurchaseId(finalPurchaseId);purchaseDetailEntity.setId(i);purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.ASSIGNED.getCode());return purchaseDetailEntity;}).collect(Collectors.toList());//批量更改采购需求,这里是MP里的接口,可直接传入对象,MP会自动读取里面的IDpurchaseDetailService.updateBatchById(collect1);//四、优化时间更新,为了显示的时间符合我们的样式PurchaseEntity purchaseEntity = new PurchaseEntity();purchaseEntity.setId(purchaseId);purchaseEntity.setUpdateTime(new Date());//五、更新采购单return this.updateById(purchaseEntity);} else {return false;}
}

 领取采购单

这里我们只用写好接口的功能,这个请求一般是由app来进行发送

controller 

/*** 领取采购单*/
@PostMapping("/received")
public R received(@RequestBody List<Long> ids){purchaseService.received(ids);return R.ok();
}

impl

领取采购单,通过接口测试工具完成请求

领取玩采购单后,更改采购单状态和对应采购需求状态

  1. 采购单状态改为已领取
  2. 采购需求状态改为正在采购
@Overridepublic void received(List<Long> ids) {//1.确认当前采购单状态List<PurchaseEntity> collect = ids.stream().map(item -> {//通过采购单id获取采购单对象PurchaseEntity purchaseEntity = this.getById(item);return purchaseEntity;}).filter(id -> {//对采购单对象进行过滤,如果状态为新建或者已分配的留下if (id.getStatus() == WareConstant.PurchaseStatusEnum.CREATED.getCode() ||id.getStatus() == WareConstant.PurchaseStatusEnum.ASSIGNED.getCode()) {return true;} else {return false;}}).map(item -> {//对上面收集好的在进行过滤,改变采购单状态为已领取(RECEIVE)item.setStatus(WareConstant.PurchaseStatusEnum.RECEIVE.getCode());//对上面收集好的在进行过滤,改变采购单更新时间item.setUpdateTime(new Date());return item;}).collect(Collectors.toList());//2.批量修改改变采购单状态this.updateBatchById(collect);//3.改变采购需求中的状态if (collect != null && collect.size() > 0) {collect.forEach(item -> {List<PurchaseDetailEntity> entities = purchaseDetailService.listDetailByPurchaseId(item.getId());List<PurchaseDetailEntity> detailEntities = entities.stream().map(entity -> {PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();purchaseDetailEntity.setId(entity.getId());//将采购需求中的状态改为正在采购purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.BUYING.getCode());return purchaseDetailEntity;}).collect(Collectors.toList());purchaseDetailService.updateBatchById(detailEntities);});}}

 image-20220814004750176

image-20220814004807416 

完成采购 

这里我们只用写好接口的功能,这个请求一般是由app来进行发送

/*** 完成采购单*/
@PostMapping("/done")
public R finished(@RequestBody PurchaseDoneVo doneVo){purchaseService.done(doneVo);return R.ok();
}VO如下:
@Data
public class PurchaseDoneVo {@NonNullprivate Long id;private List<PurchaseItemDoneVo> items;public PurchaseDoneVo(){}
}@Data
public class PurchaseItemDoneVo {private Long itemId;private Integer status;private String reason;
}

 impl

完成采购主要注意有几个地方发生了变化,做好逻辑的判断即可

/*** 采购完成一共三地方会发生变化*  1.采购单状态*  2.库存增加*  3.采购需求状态发生变化* @param doneVo*/@Overridepublic void done(PurchaseDoneVo doneVo) {//获取完成的是哪一个采购单Long id = doneVo.getId();//一、初始化Boolean flag = true;//获取采购单id集合List<PurchaseItemDoneVo> items = doneVo.getItems();//收集结果List<PurchaseDetailEntity> updates = new ArrayList<>();for (PurchaseItemDoneVo item : items) {PurchaseDetailEntity purchaseDetailEntity = new PurchaseDetailEntity();if (item.getStatus() == WareConstant.PurchaseDetailStatusEnum.HASERROR.getCode()) {flag = false;purchaseDetailEntity.setStatus(item.getStatus());} else {//二、采购需求状态发生变化purchaseDetailEntity.setStatus(WareConstant.PurchaseDetailStatusEnum.FINISH.getCode());//由采购单的id获取采购需求对象,有什么用呢?是用来给增加库存时赋值用的PurchaseDetailEntity entity = purchaseDetailService.getById(item.getItemId());//三、库存增加wareSkuService.addStock(entity.getSkuId(), entity.getWareId(), entity.getSkuNum());}//采购完成,采购需求中的状态也会发生变化,给实体类对象指明id,从而修改对象的状态purchaseDetailEntity.setId(item.getItemId());//把要修改的采购需求对象放到集合里updates.add(purchaseDetailEntity);}//因为一个采购单里有多个采购需求合并的,所以批量修改采购需求对象purchaseDetailService.updateBatchById(updates);//四.改变采购单状态PurchaseEntity purchaseEntity = new PurchaseEntity();purchaseEntity.setId(id);purchaseEntity.setStatus(flag ? WareConstant.PurchaseStatusEnum.FINISH.getCode() :WareConstant.PurchaseStatusEnum.HASERROR.getCode());purchaseEntity.setUpdateTime(new Date());this.updateById(purchaseEntity);}

 这里id = 6是对6号采购单发起操作,里面的item9和10是采购单对应的采购需求

{"id":16,"items":[{"itemId":17,"status":3,"reason":""},{"itemId":18,"status":4,"reason":"无货"}]
}

 image-20220907215915451

采购单状态如下

有异常是因为我们有一个采购单没有采购完成 

image-20220907215937701 

采购需求如下

没有完成的采购需求会显示采购失败

image-20220907220051000 

库存如下

image-20220907220103041 

显示商品库存中的sku_name 

image-20220907222230675

怎么显示呢?锁定库存就是本表库存表相关的可以直接设置,而sku_name是mall-product微服务里才能查询的到的

那就写Feign接口,这里介绍两种feign接口的写法:

给远程调用的微服务发请求

  1.  @FeignClient("mall-product") 指定微服务
     
  2.  /product/skuinfo/info/{skuId}

给网关发请求

  • @FeignClient(“mall-gateway”)
  • /api/product/skuinfo/info/{skuId}
@FeignClient("mall-gateway")
public interface ProductFeignService {@RequestMapping("/api/product/skuinfo/info/{skuId}")public R info(@PathVariable("skuId") Long skuId);}

 增加库存的时候注入FeignService接口即可实现远程调用

这里采取了try catch的形式来捕获异常,可以防止远程调用失败时,事务回滚

 

@Transactional
@Override
public void addStock(Long skuId, Long wareId, Integer skuNum) {//判断如果没有此库存记录,则为新增操作;如果有则为更改操作List<WareSkuEntity> wareSkuEntities = wareSkuDao.selectList(new QueryWrapper<WareSkuEntity>().eq("sku_id", skuId).eq("ware_id", wareId));if (wareSkuEntities == null || wareSkuEntities.size() == 0) {WareSkuEntity wareSkuEntity = new WareSkuEntity();wareSkuEntity.setSkuId(skuId);wareSkuEntity.setStock(skuNum);wareSkuEntity.setWareId(wareId);wareSkuEntity.setStockLocked(0);//TODO 远程查询sku的名字//如果查询名字查询失败了,事务回滚有点不值得,所以用trycatch来捕捉一下try {R info = productFeignService.info(skuId);Map<String,Object> skuInfo = (Map<String, Object>) info.get("skuInfo");if (info.getCode() == 0){wareSkuEntity.setSkuName((String) skuInfo.get("skuName"));}} catch (Exception e) {e.printStackTrace();}wareSkuDao.insert(wareSkuEntity);} else {wareSkuDao.addStock(skuId, wareId, skuNum);}
}

相关文章:

谷粒商城--SPU和SKU

目录 1.SPU和SKU概念 2.表的关系理解 3.导入前端代码 4.完善后端接口 5.属性分组详情 6.规格参数详情 7. 销售属性详情 8.分组与属性关联 9.发布商品 10.仓库服务 1.SPU和SKU概念 SPU&#xff1a;standard product unit(标准化产品单元)&#xff1a;是商品信息聚合的…...

二叉树OJ题(上)

✅每日一练&#xff1a;100. 相同的树 - 力扣&#xff08;LeetCode&#xff09; 题目的意思是俩棵树的结构不仅要相同&#xff0c;而且每个节点的值还要相同&#xff0c;如果满足上面2个条件&#xff0c;则成立&#xff01; 解题思路&#xff1a; 从三个方面去考虑&#xff1…...

第一章 PDF语法

第一章 PDF语法PDF ObjectsNull ObjectsBoolean ObjectsNumeric ObjectsName ObjectsString ObjectsArray ObjectsDictionary ObjectsName treesNumber treesStream ObjectsDirect versus Indirect ObjectsFile StructureWhite-SpaceThe Four Sections of a PDFHeaderTrailerBo…...

IntelliJ IDEA 创建JavaFX项目运行

IntelliJ IDEA 创建JavaFX项目运行JavaFX官网文档&#xff1a;https://openjfx.io/openjfx-docs/ JavaFX 2008年12月05日诞生&#xff0c;是一个开源的下一代客户端应用程序平台&#xff0c;适用于基于 Java 构建的桌面、移动和嵌入式系统。这是许多个人和公司的协作努力&#…...

IC封装常见形式

参考&#xff1a;https://blog.csdn.net/dhs888888/article/details/127673300?utm_mediumdistribute.pc_relevant.none-task-blog-2defaultbaidujs_baidulandingword~default-0-127673300-blog-115610343.pc_relevant_multi_platform_whitelistv4&spm1001.2101.3001.4242…...

Linux通配符、转义符讲解

目录 通配符 通过通配符定义匹配条件 转义符 将所有的逻辑操作符都转换成字符 通配符 通过通配符定义匹配条件 * 任意字符都可以通配&#xff08;也可以匹配空值&#xff09; &#xff1f; 匹配单个字符 [a-z] 匹配单个的小写英文字母 [A-Z] 匹配单个的大写英文…...

[OpenMMLab]提交pr时所需的git操作

git开发流程 准备工作 作为一个开发者&#xff0c;fork一个仓库之后应该先做什么&#xff1f; 1、下载仓库&#xff0c;创建上游代码库&#xff0c;查看当前的分支情况 git clone https://github.com/<your_name>/<repo_name>.git git remote add upstream git…...

pandas——groupby操作

Pandas——groupby操作 文章目录Pandas——groupby操作一、实验目的二、实验原理三、实验环境四、实验内容五、实验步骤一、实验目的 熟练掌握pandas中的groupby操作 二、实验原理 groupby(byNone, axis0, levelNone, as_indexTrue, sortTrue, group_keysTrue, squeezeFalse&…...

webpack.config.js哪里找?react项目关闭eslint监测

目录 webpack.config.js哪里找&#xff1f; react项目关闭eslint监测 webpack.config.js哪里找&#xff1f; 在React项目中&#xff0c;当我们需要修改一些配置时&#xff0c;发现找不到webpack.config.js&#xff0c;是我们创建的项目有问题吗&#xff0c;还需新创建项目的项…...

OpenCV 图像梯度算子

本文是OpenCV图像视觉入门之路的第12篇文章&#xff0c;本文详细的介绍了图像梯度算子的各种操作&#xff0c;例如&#xff1a;Sobel算子Scharr算子laplacian算子等操作。 OpenCV 图像梯度算子目录 1 Sobel算子 2 Scharr算子 3 laplacian算子 1 Sobel算子 Sobel算子是一种图…...

Linux c编程之Wireshark

Wireshark是一个网络报文分析软件,是网络应用问题分析必不可少的工具软件。网络管理员可以使用wireshark排查网络问题。程序开发人员可以用来分析应用协议、定位分析应用问题。无论是网络应用程序开发人员、测试人员、部署人员、技术支持人员,掌握wireshark的使用对于分析网络…...

极客时间_FlinkSQL 实战

一、批处理以及流处理技术发展 1.Lambda架构三层划分Batch Layer、Speed Layer和Serving Layer。 ①、Batch Layer:主要用于实现对历史数据计算结果的保存,每天计算的结果都保存成为一个Batch View,然后通过对Batch View的计算,实现历史数据的计算。 ②、Speed Layer正是用…...

Pytorch 混合精度训练 (Automatically Mixed Precision, AMP)

Contents混合精度训练 (Mixed Precision Training)单精度浮点数 (FP32) 和半精度浮点数 (FP16)为什么要用 FP16为什么只用 FP16 会有问题解决方案损失缩放 (Loss Scaling)FP32 权重备份黑名单Tensor CoreNVIDIA apex 库代码解读opt-level (o1, o2, o3, o4)apex 的 o1 实现apex …...

使用太极taichi写一个只有一个三角形的有限元

公式来源 https://blog.csdn.net/weixin_43940314/article/details/128935230 GAME103 https://games-cn.org/games103-slides/ 初始化我们的三角形 全局的坐标范围为0-1 我们的三角形如图所示 ti.kernel def init():X[0] [0.5, 0.5]X[1] [0.5, 0.6]X[2] [0.6, 0.5]x[0…...

进程,线程

进程是操作系统分配资源的基本单位&#xff0c;线程是CPU调度的基本单位。 PCB&#xff1a;进程控制块&#xff0c;操作系统描述程序的运行状态&#xff0c;通过结构体task,struct{…}&#xff0c;统称为PCB&#xff08;process control block&#xff09;。是进程管理和控制的…...

第03章_基本的SELECT语句

第03章_基本的SELECT语句 讲师&#xff1a;尚硅谷-宋红康&#xff08;江湖人称&#xff1a;康师傅&#xff09; 官网&#xff1a;http://www.atguigu.com 1. SQL概述 1.1 SQL背景知识 1946 年&#xff0c;世界上第一台电脑诞生&#xff0c;如今&#xff0c;借由这台电脑发展…...

干货 | 简单了解运算放大器...

运算放大器发明至今已有数十年的历史&#xff0c;从最早的真空管演变为如今的集成电路&#xff0c;它在不同的电子产品中一直发挥着举足轻重的作用。而现如今信息家电、手机、PDA、网络等新兴应用的兴起更是将运算放大器推向了一个新的高度。01 运算放大器简述运算放大器&#…...

C++定位new用法及注意事项

使用定位new创建对象&#xff0c;显式调用析构函数是必须的&#xff0c;这是析构函数必须被显式调用的少数情形之一&#xff01;&#xff0c; 另有一点&#xff01;&#xff01;&#xff01;析构函数的调用必须与对象的构造顺序相反&#xff01;切记&#xff01;&#xff01;&a…...

【Android笔记75】Android之翻页标签栏PagerTabStrip组件介绍及其使用

这篇文章,主要介绍Android之翻页标签栏PagerTabStrip组件及其使用。 目录 一、PagerTabStrip翻页标签栏 1.1、PagerTabStrip介绍 1.2、PagerTabStrip的使用 (1)创建布局文件...

【Kafka】【二】消息队列的流派

消息队列的流派 ⽬前消息队列的中间件选型有很多种&#xff1a; rabbitMQ&#xff1a;内部的可玩性&#xff08;功能性&#xff09;是⾮常强的rocketMQ&#xff1a; 阿⾥内部⼀个⼤神&#xff0c;根据kafka的内部执⾏原理&#xff0c;⼿写的⼀个消息队列中间 件。性能是与Kaf…...

现代 cmake (cmake 3.x) 操作大全

cmake 是一个跨平台编译工具&#xff0c;它面向各种平台提供适配的编译系统配置文件&#xff0c;进而调用这些编译系统完成编译工作。cmake 进入3.x 版本&#xff0c;指令大量更新&#xff0c;一些老的指令开始被新的指令集替代&#xff0c;并加入了一些更加高效的指令/参数。本…...

how https works?https工作原理

简单一句话&#xff1a; https http TLShttps 工作原理&#xff1a;HTTPS (Hypertext Transfer Protocol Secure)是一种带有安全性的通信协议&#xff0c;用于在互联网上传输信息。它通过使用加密来保护数据的隐私和完整性。下面是 HTTPS 的工作原理&#xff1a;初始化安全会…...

Docker的资源控制管理

目录 一、CPU控制 1、设置CPU使用率上限 2、设置CPU资源占用比&#xff08;设置多个容器时才有效&#xff09; 3、设置容器绑定指定的CPU 二、对内存使用进行限制 1、创建指定物理内存的容器 2、创建指定物理内存和swap的容器 3、 对磁盘IO配额控制&#xff08;blkio&a…...

MMSeg无法使用单类自定义数据集训练

文章首发及后续更新&#xff1a;https://mwhls.top/4423.html&#xff0c;无图/无目录/格式错误/更多相关请至首发页查看。 新的更新内容请到mwhls.top查看。 欢迎提出任何疑问及批评&#xff0c;非常感谢&#xff01; 摘要&#xff1a;将三通道图像转为一通道图像&#xff0c;…...

Redis使用方式

一、Redis基础部分: 1、redis介绍与安装比mysql快10倍以上 *****************redis适用场合**************** 1.取最新N个数据的操作 2.排行榜应用,取TOP N 操作 3.需要精确设定过期时间的应用 4.计数器应用 5.Uniq操作,获取某段时间所有数据排重值 6.实时系统,反垃圾系统7.P…...

无主之地3重型武器节奏评分榜(9.25) 枪械名 红字效果 元素属性 清图评分 Boss战评分 泛用性评分 特殊性评分 最终评级 掉落点 掉率 图片 瘟疫传播

无主之地3重型武器节奏评分榜&#xff08;9.25&#xff09; 枪械名 红字效果 元素属性 清图评分 Boss战评分 泛用性评分 特殊性评分 最终评级 掉落点 掉率 图片 瘟疫传播者 发射巨大能量球&#xff0c;能量球会额外生成追踪附近敌人的伴生弹 全属性 SSS SSS SSS - T0 伊甸6号-…...

什么是编程什么是算法

1.绪论 编程应在一个开发环境中完成源程序的编译和运行。首先,发现高级语言开发环境,TC,Windows系统的C++,R语言更适合数学专业的学生。然后学习掌握编程的方法,在学校学习,有时间的人可以在网上学习,或者购买教材自学。最后,编写源程序,并且在开发环境中实践。 例如…...

【c++】函数

文章目录函数的定义函数的调用值传递常见样式函数的声明函数的分文件编写函数的作用&#xff1a; 将一段经常使用的代码封装起来&#xff0c;减少重复代码。 一个较大的程序&#xff0c;一般分为若干个程序块&#xff0c;每个模板实现特定的功能。 函数的定义 返回值类型 函数…...

[golang gin框架] 1.Gin环境搭建,程序的热加载,路由GET,POST,PUT,DELETE

一.Gin 介绍Gin 是一个 Go (Golang) 编写的轻量级 http web 框架&#xff0c;运行速度非常快&#xff0c;如果你是性能和高效的追求者&#xff0c;推荐你使用 Gin 框架.Gin 最擅长的就是 Api 接口的高并发&#xff0c;如果项目的规模不大&#xff0c;业务相对简单&#xff0c;这…...

【开源】祁启云网络验证系统V1.11

简介 祁启云免费验证系统 一个使用golang语言、Web框架beego、前端Naive-Ui-Admin开发的免费网络验证系统 版本 当前版本1.11 更新方法 请直接将本目录中的verification.exe/verification直接覆盖到你服务器部署的目录&#xff0c;更新前&#xff0c;请先关闭正在运行的验…...

如何建淘客网站/网站制作推广

打开idea的Terminal,输入 npm install -g webpack webpack-cli...

阿里云是不是做网站的/企业网站设计素材

php有array_merge合并数组功能&#xff0c;但是有个问题&#xff0c;这个函数只合并一维数组&#xff0c;如果有多维数组&#xff0c;则后面的会覆盖前面数组中的子节点&#xff0c;因此写啦另一个深度合并的函数来合并子节点的元素/*** 深度合并数组* DateTime 2020-01-11* Au…...

seo快速提高网站转化率/凡科官网免费制作小程序

2019独角兽企业重金招聘Python工程师标准>>> svn代码版本管理1.0开发&#xff0c;做dev1.0的branch此时的目录结构svn://proj/ trunk/ (不负担开发任务) branches/ dev_1.0 (copy from trunk) tags/…...

xampp php网站模板/东莞搜索seo网站关键词优化

adb&#xff1a;Android Debug Bridge&#xff08;安卓调试&#xff09; 1.安装apk到android手机中&#xff08;手机连上电脑&#xff09; adb install xxx.apk 如果安装apk到rom中&#xff0c;提示 “exsit 。。。” 的提示信息&#xff0c;可以加上 - r 参数 &#xff…...

重庆网站推广哪家好/拓客引流推广

JetCache学习笔记 - lwh147 - 博客园JetCache学习笔记和问题记录https://www.cnblogs.com/lwh147/p/15176574.html 在分布式系统中&#xff0c;使用redis的原子性操作实现分布式锁&#xff0c;不过在自己编辑代码的时候&#xff0c;会出现一些代码上的问题&#xff0c;这个工具…...

西安做网站的公司电话/深圳优化公司哪家好

同步发表&#xff1a;http://blog.hacktons.cn/2017/12/13/shell-func-return/ 背景 通过shell编程&#xff0c;写一些工具批处理的时候&#xff0c;经常需要自定义函数。更复杂点的情况下&#xff0c;可能有需要返回一个值。 由于在shell的世界中&#xff0c;并不像其他编程语…...