谷粒商城--SPU和SKU

Posted 最小的帆也能远航

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了谷粒商城--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属性表

商品属性表和属性表通过attridid进行关联,能查出每个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到放到一个集合里

怎样写好递归?

  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

 


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;

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

 销售属性回显

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

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

 

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

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.分组与属性关联

显示属性 

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

  1. 点击分组属性的时候获取到分组id,
  2. 拿分组id去关联表查分组id对应的attr_id
  3. 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) 
    //分布查询,第一步去关联表中查出所有的组和属性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;

测试

移除属性 

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

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>

查询分组未关联的属性

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;

 注意非空判断

测试

 

给销售属性绑定分组,把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;

测试

开发规范

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

 

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

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

 

@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是最好的选择,不需要对实体类字段进行修改

 

/**
     * 获取分类下的所有分组及属性
     * @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;
    

 测试

商品新增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_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. 加入网关

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

 

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

多条件分页查询 

@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

以上是关于谷粒商城--SPU和SKU的主要内容,如果未能解决你的问题,请参考以下文章

谷粒商城笔记+踩坑——上架商品spu到ES索引库

Day409.商品上架 -谷粒商城

第七篇商城系统-商品发布-SKU和SPU管理

第七篇商城系统-商品发布-SKU和SPU管理

第五篇商城系统-商品属性管理

第五篇商城系统-商品属性管理