谷粒商城--SPU和SKU

news2024/9/24 3:29:05

目录

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;

@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);
            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) {
    //分布查询,第一步去关联表中查出所有的组和属性id
    List<AttrAttrgroupRelationEntity> entities = relationService.list(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id",attrgroupId));

    //第二收集属性id
    List<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
     */
    @TableId
    private 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
     */
    @Override
    public 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_value
        List<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中的信息
    //如果指定了采购单,那就获取采购单的id
    Long purchaseId = mergeVo.getPurchaseId();
    //获得采购需求的id
    List<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);
            //获得自动创建的采购单id
            purchaseId = 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会自动读取里面的ID
        purchaseDetailService.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. 采购需求状态改为正在采购
@Override
    public 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 {
    @NonNull
    private 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
     */
    @Override
    public 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);
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/340928.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

链表OJ(一)

目录 从尾到头打印链表_牛客题霸_牛客网 160. 相交链表 141. 环形链表 142. 环形链表 II 138. 复制带随机指针的链表 从尾到头打印链表_牛客题霸_牛客网 输入一个链表的头节点&#xff0c;按链表从尾到头的顺序返回每个节点的值&#xff08;用数组返回&#xff09;。 如输入…

Three.js中的3D文字效果

对于一些设计网页中经常会出现一些3D的文字效果&#xff0c;本文将利用Three.js实现各种动画WebGL文本输入效果。 示例效果 原文章 文本采样 通常情况下&#xff0c;文本网格是2D的平面形状&#xff0c;我们所要实现的3D文本形状则是要在2D的平面下&#xff0c;再生成z值形成…

Oracle数据库启停命令

在日常工作中&#xff0c;关于数据库的启停&#xff1a;   先关闭上层应用服务 --> 关闭监听 --> 关闭数据库&#xff1b;   启动数据库 --> 启动监听 --> 启动应用(Oracle SQL Developer)。 监听lsnrctl Oracle监听命令。 lsnrctl start [listener-name]&a…

Xml格式化与高亮显示

具体请参考&#xff1a;Xml格式化与高亮显示

分布式系统的数据一致性方案

1、在出现一致性问题时如果系统的并发或不一致情况较少&#xff0c;可以先使用重试来解决 a、同步重试 b、异步重试 c、入库&#xff0c;定时任务重试 2、分布式事务 基于数据库 XA 协议的 2PC、3PC&#xff0c;基于业务层的TCC,基于消息队列消息表的最终一致性方案&#xff0…

七大设计原则之接口隔离原则应用

目录1 接口隔离原则介绍2 接口隔离原则应用1 接口隔离原则介绍 接口隔离原则&#xff08;Interface Segregation Principle, ISP&#xff09;是指用多个专门的接口&#xff0c;而不使用单一的总接口&#xff0c;客户端不应该依赖它不需要的接口。这个原则指导我们在设计接口时…

八、Git远程仓库操作——跨团队成员的协作

前言 前面一篇博文介绍了git团队成员之间的协作&#xff0c;现在在介绍下如果是跨团队成员的话&#xff0c;如何协作&#xff1f; 跨团队成员协作&#xff0c;其实就是你不属于那个项目的成员&#xff0c;你没有权限向那个仓库提交代码。但是github还有另一种 pull request&a…

图形化深度学习开发平台PaddleStudio(代码开源)

目录一、PaddleStudio概述二、环境准备2.1 安装PaddlePaddle2.2 安装依赖库三、基本使用介绍3.1 启动3.2 快速体验3.2.1 下载示例项目3.2.2 训练3.2.3 评估3.2.4 测试3.2.5 静态图导出四、数据集格式4.1 图像分类4.2 目标检测4.3 语义分割4.4 实例分割五、趣味项目实战&#xf…

【前端笔试题二】从一个指定数组中,每次随机取一个数,且不能与上次取数相同,即避免相邻取数重复

前言 本篇文章记录下我在笔试过程中遇到的真实题目&#xff0c;供大家参考。 1、题目 系统给定一个数组&#xff0c;需要我们编写一个函数&#xff0c;该函数每次调用&#xff0c;随机从该数组中获取一个数&#xff0c;且不能与上一次的取数相同。 2、思路解析 数组已经有了…

Java编译过程、JIT编译详解、类加载过程

文章目录Java编译执行过程类加载过程即时编译JITJIT编译优化中的常见技术方法内联逃逸分析 栈上分配 锁消除小总结Java编译执行过程 提到编译,可能大多数人想到的就是将**.java编译成***.class文件,但其实Java代码的编译执行是一个非常复杂的过程,将**.java编译成**.class…

Lesson1:初识编程语言、Python环境搭建

一、什么是编程语言 用来和计算机交流&#xff0c;控制计算机&#xff0c;让计算机按照我们的要求做事情&#xff0c;这样的语言叫做编程语言。 Note&#xff1a;编程语言四个字可以拆成两个部分进行理解——编程语言。 所谓语言&#xff0c;它的作用就是交流&#xff0c;向对…

Redis实战—黑马点评(一) 登录篇

Redis实战 — 黑马点评&#xff08;一&#xff09; 登录篇 来自黑马的redis课程的笔记 【黑马程序员Redis入门到实战教程&#xff0c;深度透析redis底层原理redis分布式锁企业解决方案黑马点评实战项目】 目录Redis实战 — 黑马点评&#xff08;一&#xff09; 登录篇1. 项目…

深度学习笔记:使用随机梯度下降法识别mnist数据集

深度学习算法实现流程&#xff1a; 1 从训练数据中随机选出一部分数据&#xff0c;称为mini-batch。我们的目标为减小mini-batch损失函数的值 2 计算损失函数关于权重的梯度。梯度方向即为损失函数值减小最快的方向 3 将权重沿梯度下降方向更新 4 重复以上步骤&#xff0c;在…

【自动驾驶汽车技术 | 车载雷达系统】

本文编辑&#xff1a;调皮哥的小助理 1、摘要 自动驾驶汽车传感器系统一般包括4种雷达&#xff1a;激光雷达(Lidar)、毫米波雷达(mmWave Radar)、超声波雷达(Ultrasonic Radar)和红外雷达(Infrared Radar)。目前激光雷达和毫米波雷达是基本和必要的车载传感器设备&#xff0c;…

I.MX6ULL内核开发8:linux设备驱动模型

目录 一、为什么需要设备驱动模型 二、sysfs概述 驱动模型一 驱动模型二 kobject kset kobj_type 一、为什么需要设备驱动模型 早期内核&#xff08;2.4之前&#xff09;没有统一的设备驱动模型&#xff0c;但是照样可以使用&#xff08;之前的led字符设备驱动&#xff…

2023-2-12刷题情况

字母板上的路径 题目描述 我们从一块字母板上的位置 (0, 0) 出发&#xff0c;该坐标对应的字符为 board[0][0]。 在本题里&#xff0c;字母板为board [“abcde”, “fghij”, “klmno”, “pqrst”, “uvwxy”, “z”]&#xff0c;如下所示。 我们可以按下面的指令规则行动…

合宙Air103|fbd数据库| fskv - 替代fdb库|LuatOS-SOC接口|官方demo|学习(16):类redis的fbd数据库及fskv库

基础资料 基于Air103开发板&#xff1a;&#x1f697; Air103 - LuatOS 文档 上手&#xff1a;开发上手 - LuatOS 文档 探讨重点 对官方社区库接口类redis的fbd数据库及fskv库的调用及示例进行复现及分析&#xff0c;了解两库的基本原理及操作方法。 软件及工具版本 Luat…

肝了几天的Git入门教程,收获满满

1.简介 谈及版本控制系统&#xff0c;或许大多数程序员最开始接触的都是SVN&#xff08;Subversion&#xff09;&#xff0c;它是一个集中式的版本控制系统&#xff0c;使用的时候需要提供一台的服务器来进行部署&#xff0c;所有的更新与同步操作都需要与这台服务器进行交互&…

windows/linux下Qt可执行程序打包,linux桌面双击运行程序sh脚本

1、windows下Qt打包 windows下Qt的可执行文件打包简单的来说就是利用Qt自带依赖的打包工具windeployqt进行打包&#xff0c;该工具存在Qt安装目录下&#xff0c;执行命令为&#xff1a;windeployqt name.exe 打包依赖文件可参考如下链接中1-7步&#xff0c;后面的步骤是打包依…

156、【动态规划】AcWing ——3. 完全背包问题:二维数组+一维滚动数组(C++版本)

题目描述 原题链接&#xff1a;3. 完全背包问题 解题思路 完全背包相对于01背包来说&#xff0c;对同一个物品可以选择多次。而01背包对同一个物品只能选择一次。 递推公式上的区别&#xff1a;01背包是dp[i][j] max(dp[i - 1][j], dp[i - 1][j - v[i]] w[i])&#xff0c;…