谷粒商城--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的属性。它们都属于是规格参数
像版本,颜色等都属是SKU的销售属性
2.表的关系理解
属性关系-规格参数-销售属性-三级分类 关联关系
每个三级分类下有各自的属性分组表通过
id和catelogid关联
,能查出每个分类下的属性分组属性分组表和属性表通过一个
属性&属性关联表
进行关联,能查出每个属性分组下的属性最终这样的关系我们可以查出每个分类的
属性分组
和每个属性分组对应的属性
通过思维导图来理解
手机是一级分类,它下面又有属性组,每个属性组又有各自的属性
SPU-SKU属性表
商品属性表和属性表通过
attrid
和id
进行关联,能查出每个spu的属性sku销售属性表是为了表示
spu下不同sku
,比如1号spu在此表有两个sku,这两个sku有不同的销售属性,是通过和属性表关联获取
的
通过思维导图来理解
像网络、像素一般是固定不可选的所以是SPU属性
而内存、容量、颜色等可选的就为SKU销售属性
3.导入前端代码
重新执行“sys_menus.sql”,完善菜单
正常我们是在系统管理里自定义添加,步骤都是一样的,其实在前端页面添加就是把数据提交到mall_admin表中,这里我们直接把提供的sql语句导入即可!
如下结果:
实现点击菜单的左边,能够实现在右边展示数据
这个页面就是三级分类和一个表格显示在一块对吧,属于是
父子组件交互
前端不具体写了,我们直接导入代码,效果如下:
4.完善后端接口
什么是开发接口
开发接口就是开发Controller、service、dao
在线接口文档如下
03、获取分类属性分组 - 谷粒商城谷粒商城 - 03、获取分类属性分组,http GET /product/attrgroup/list/{catelogId},,技术团队的文档管理平台,接口文档工具,支持在线接口调试,一键生成API文档,适合编写接口文档、产品文档、使用手册
https://easydoc.net/s/78237135/ZUqEdvA4/OXTgKobR
别人告诉你需要什么功能,需要返回什么样的数据,你就通过接口的形式把他们呢实现出来即可!以后工作了也是这种形式,主要是开发接口为多,前端其实不用写太多,能看懂即可
5.属性分组详情
显示属性分组
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);}
}
属性分组回显
这一部分主要是做属性分组的数据回显的
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到放到一个集合里
怎样写好递归?
- 确定参数值和返回值
- 确定终止条件
- 递归逻辑
三者缺一不可!!!
//找到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
6.规格参数详情
接口如下
什么是规格参数
保存规格参数
/*** 保存*/
@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;
}
测试
这些属性的分类和所属分组怎么查呢
规格参数表(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;
}
测试
规格参数回显
可以看出所属分类和分组都是由这条请求查询的,那么我们改这个接口功能就行
相当于在
原来查询基础上
返回分类路径信息
,分组信息
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;
}
测试
修改Or增加
提交修改分类和分组是无效的?
更改用的还是默认的update方法,所以我们改update接口!
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=
这个接口有问题!所以我们就去后端改这个接口即可!
controller
规格参数和销售参数的区别在于type的值,
type为 1是规格参数
,type为0是销售参数
这里采用一个方法当两个来用!
@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;}
销售属性回显
可以看到,销售属性回显是不需要所属分组的
但是
销售属性
和规格参数
用的是同一个回显方法
,我们也进行更改,只有是规格参数的时候才进行分组回显
!
在原分组回显的逻辑上加上判断,后面逻辑不变
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {...
}
修改销售属性
销售属性
和规格参数
用的是同一个修改方法
,销售属性进行修改时,会对关联表进行一个级联更新,但销售属性不需要所以也在对关联表级联更新的时候进行判断,只有销售属性修改的时候才进行级联更新!
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {...
}
保存销售属性
销售属性
和规格参数
用的是同一个保存方法
,销售属性进行保存时,会对关联表进行一个级联保存,但销售属性不需要所以也在对关联表级联保存的时候进行判断,只有销售属性保存的时候才进行级联保存!
if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) {...
}
8.分组与属性关联
显示属性
这里其实就是一个分布查询,流程如下:
- 点击分组属性的时候获取到分组id,
- 拿分组id去关联表查分组id对应的attr_id
- 拿
attr_id
去pms_attr表中获取属性
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;
}
测试
移除属性
这里为了方便,我们直接写一个批量删除的接口
controller
- /product/attrgroup/attr/relation/delete
- post请求会带来json数据,要封装成自定义对象vos需要@RequestBody注解
- 意思就是将请求体中的数据封装成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>
查询分组未关联的属性
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
认真看注释,认真理解,还是很绕的
查询分组未关联的数据三步!
- 获得当前分类下的所有分组
- 获得这些分组下所有已添加的属性
- 添加新属性时移除这些已添加的属性
@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;
}
注意非空判断
测试
给销售属性绑定分组,把9号属性绑定给1号分组
查询分组未关联的属性
添加属性关联
常规的调用,注意点是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.发布商品
调试会员等级接口
启动会员微服务,添加网关,添加前端页面…
添加如下会员:
获取分类关联的品牌
/*** 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;
}
测试
开发规范
- Controller:处理请求,接受和校验数据
- Service接受controller传来的数据,进行业务处理
- Controller接受service处理完的数据,封装页面指定的vo
获取分类下所有分组&关联属性
也就是说当我们选择手机分类时,那就查出手机相关的分组信息,并查出每个分组相应属性信息
@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是最好的选择,不需要对实体类字段进行修改
/*** 获取分类下的所有分组及属性* @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;}
测试
商品新增vo抽取
设置完属性,点击保存之后取消保存,复制控制台输出
在线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没有的那就可以自己赋值
表结构如下:
这里的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服务&获取仓库列表
- 加入微服务注册中心
- 加入网关
获取仓库列表就是对仓库表的简单查询,逆向生成代码以帮我们生成好,只要配置好网关就可以直接显示
我们只要记住,反是单表操作的逆向生成以帮我们生成好了,我们能拿来直接用,就像增加仓库、删除、修改都是可以直接用的
多条件分页查询
@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);
}
合并采购需求
创建采购单
合并请求接口
这里有两种情况如下:
- 如果没有选中采购单,那么会自动创建采购单进行合并
- 有的话,就用采购单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
领取采购单,通过接口测试工具完成请求
领取玩采购单后,更改采购单状态和对应采购需求状态
- 采购单状态改为已领取
- 采购需求状态改为正在采购
@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);});}}
完成采购
这里我们只用写好接口的功能,这个请求一般是由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":"无货"}]
}
采购单状态如下
有异常是因为我们有一个采购单没有采购完成
采购需求如下
没有完成的采购需求会显示采购失败
库存如下
显示商品库存中的sku_name
怎么显示呢?锁定库存就是本表库存表相关的可以直接设置,而sku_name是mall-product微服务里才能查询的到的
那就写Feign接口,这里介绍两种feign接口的写法:
给远程调用的微服务发请求
- @FeignClient("mall-product") 指定微服务
- /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:standard product unit(标准化产品单元):是商品信息聚合的…...

二叉树OJ题(上)
✅每日一练:100. 相同的树 - 力扣(LeetCode) 题目的意思是俩棵树的结构不仅要相同,而且每个节点的值还要相同,如果满足上面2个条件,则成立! 解题思路: 从三个方面去考虑࿱…...

第一章 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官网文档:https://openjfx.io/openjfx-docs/ JavaFX 2008年12月05日诞生,是一个开源的下一代客户端应用程序平台,适用于基于 Java 构建的桌面、移动和嵌入式系统。这是许多个人和公司的协作努力&#…...

IC封装常见形式
参考: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通配符、转义符讲解
目录 通配符 通过通配符定义匹配条件 转义符 将所有的逻辑操作符都转换成字符 通配符 通过通配符定义匹配条件 * 任意字符都可以通配(也可以匹配空值) ? 匹配单个字符 [a-z] 匹配单个的小写英文字母 [A-Z] 匹配单个的大写英文…...
[OpenMMLab]提交pr时所需的git操作
git开发流程 准备工作 作为一个开发者,fork一个仓库之后应该先做什么? 1、下载仓库,创建上游代码库,查看当前的分支情况 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哪里找? react项目关闭eslint监测 webpack.config.js哪里找? 在React项目中,当我们需要修改一些配置时,发现找不到webpack.config.js,是我们创建的项目有问题吗,还需新创建项目的项…...

OpenCV 图像梯度算子
本文是OpenCV图像视觉入门之路的第12篇文章,本文详细的介绍了图像梯度算子的各种操作,例如: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…...

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

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

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

C++定位new用法及注意事项
使用定位new创建对象,显式调用析构函数是必须的,这是析构函数必须被显式调用的少数情形之一!, 另有一点!!!析构函数的调用必须与对象的构造顺序相反!切记!!&a…...
【Android笔记75】Android之翻页标签栏PagerTabStrip组件介绍及其使用
这篇文章,主要介绍Android之翻页标签栏PagerTabStrip组件及其使用。 目录 一、PagerTabStrip翻页标签栏 1.1、PagerTabStrip介绍 1.2、PagerTabStrip的使用 (1)创建布局文件...
【Kafka】【二】消息队列的流派
消息队列的流派 ⽬前消息队列的中间件选型有很多种: rabbitMQ:内部的可玩性(功能性)是⾮常强的rocketMQ: 阿⾥内部⼀个⼤神,根据kafka的内部执⾏原理,⼿写的⼀个消息队列中间 件。性能是与Kaf…...

高频面试之3Zookeeper
高频面试之3Zookeeper 文章目录 高频面试之3Zookeeper3.1 常用命令3.2 选举机制3.3 Zookeeper符合法则中哪两个?3.4 Zookeeper脑裂3.5 Zookeeper用来干嘛了 3.1 常用命令 ls、get、create、delete、deleteall3.2 选举机制 半数机制(过半机制࿰…...
CRMEB 框架中 PHP 上传扩展开发:涵盖本地上传及阿里云 OSS、腾讯云 COS、七牛云
目前已有本地上传、阿里云OSS上传、腾讯云COS上传、七牛云上传扩展 扩展入口文件 文件目录 crmeb\services\upload\Upload.php namespace crmeb\services\upload;use crmeb\basic\BaseManager; use think\facade\Config;/*** Class Upload* package crmeb\services\upload* …...
Device Mapper 机制
Device Mapper 机制详解 Device Mapper(简称 DM)是 Linux 内核中的一套通用块设备映射框架,为 LVM、加密磁盘、RAID 等提供底层支持。本文将详细介绍 Device Mapper 的原理、实现、内核配置、常用工具、操作测试流程,并配以详细的…...

【7色560页】职场可视化逻辑图高级数据分析PPT模版
7种色调职场工作汇报PPT,橙蓝、黑红、红蓝、蓝橙灰、浅蓝、浅绿、深蓝七种色调模版 【7色560页】职场可视化逻辑图高级数据分析PPT模版:职场可视化逻辑图分析PPT模版https://pan.quark.cn/s/78aeabbd92d1...

Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习)
Aspose.PDF 限制绕过方案:Java 字节码技术实战分享(仅供学习) 一、Aspose.PDF 简介二、说明(⚠️仅供学习与研究使用)三、技术流程总览四、准备工作1. 下载 Jar 包2. Maven 项目依赖配置 五、字节码修改实现代码&#…...

【VLNs篇】07:NavRL—在动态环境中学习安全飞行
项目内容论文标题NavRL: 在动态环境中学习安全飞行 (NavRL: Learning Safe Flight in Dynamic Environments)核心问题解决无人机在包含静态和动态障碍物的复杂环境中进行安全、高效自主导航的挑战,克服传统方法和现有强化学习方法的局限性。核心算法基于近端策略优化…...
Java求职者面试指南:计算机基础与源码原理深度解析
Java求职者面试指南:计算机基础与源码原理深度解析 第一轮提问:基础概念问题 1. 请解释什么是进程和线程的区别? 面试官:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位;而线程是进程中的…...

LLMs 系列实操科普(1)
写在前面: 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容,原视频时长 ~130 分钟,以实操演示主流的一些 LLMs 的使用,由于涉及到实操,实际上并不适合以文字整理,但还是决定尽量整理一份笔…...
08. C#入门系列【类的基本概念】:开启编程世界的奇妙冒险
C#入门系列【类的基本概念】:开启编程世界的奇妙冒险 嘿,各位编程小白探险家!欢迎来到 C# 的奇幻大陆!今天咱们要深入探索这片大陆上至关重要的 “建筑”—— 类!别害怕,跟着我,保准让你轻松搞…...

【JVM】Java虚拟机(二)——垃圾回收
目录 一、如何判断对象可以回收 (一)引用计数法 (二)可达性分析算法 二、垃圾回收算法 (一)标记清除 (二)标记整理 (三)复制 (四ÿ…...