Skip to content

Spring Cloud 谷粒商城学习记录(下)

商品服务模块

接下来我们需要了解商品相关表的设计思想,可以说表的设计是一个业务的核心驱动,电商表也是十分的复杂,下面要学习点商表的设计。

概念-SPU和SKU

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

SKU=Stock Keeping Unit(库存量单位)。即库存进出计量的基本单元,可以是以件,盒,托盘等为单位。SKU这是对于大型连锁超市DC(配送中心)物流管理的一个必要的方法。现在已经被引申为产品统一编号的简称,每种产品均对应有唯一的SKU号。 比如,咱们购买一台iPhoneX手机,iPhoneX手机就是一个SPU,但是你购买的时候,不可能是以iPhoneX手机为单位买的,商家也不可能以iPhoneX为单位记录库存。必须要以什么颜色什么版本的iPhoneX为单位。比如,你购买的是一台银色、128G内存的、支持联通网络的iPhoneX ,商家也会以这个单位来记录库存数。那这个更细致的单位就叫库存单元(SKU)。

如上图,一般的电商系统你点击进去以后,都能看到这个商品关联了其他好几个类似的商品,而且这些商品很多的信息都是共用的,比如商品图片,海报、销售属性(颜色)等。

概念-基本属性和销售属性

能完全决定售价和库存量的属性,称为销售属性,例如颜色、版式、套装等属性。

 

在同一个SPU下无论我们切换什么版本,颜色,都会共享的属性,用于描述这一类产品(SPU)的共同特点,例如重量、屏幕、电池容量等等,不同版本的同一个SPU它们都具备的公共属性,我们就称之为基本属性。这类产品它们都具备这些属性字段,只是参数值不一样。基本属性也有自己的分组,例如主体、基本信息、存储、屏幕等。

 

当我们选择不同的SKU时可以发现它们的商品介绍,以及规格参数都是相同的,可以知道商品介绍以及规格和包装(规格参数)是与SPU关联的,都是SPU的特性,都代表了这一类商品共同具备的特点参数。

 

我们换一个手机商品后可以发现,基本属性(规格与包装)和销售属性字段都是相同的,只是具体的值不同,我们可以得知:无论是基本属性(规格与包装)还是销售属性都是与三级分类相关联的,例如这里的的规格参数都是与“手机”这个分类相关联的,并且基本属性的分组也是和三级分类相关联的,例如主体、基本信息、存储信息等分组都适用于“手机”这个三级分类。

并且我们可以在首页看到,一些基本属性是有提供检索筛选的

 

每一个SKU都有一个唯一ID来标识

 

表设计需要结合实际的业务,接下来我们总结一下:

  • SPU是用来决定规格参数(规格和包装、商品介绍等)的值,SKU是用来决定销售属性的值。

  • 无论是基本属性(规格与包装)还是销售属性都是与三级分类相关联的,并且基本属性的分组也是和三级分类相关联的。

  • 每一个SKU都需要有一个唯一ID来标识

  • 一些规格参数是需要提供检索筛选的

商品相关表设计

商品属性表(pms_attr)

属性表即是前面用来存放规格参数字段的表,同一类产品SPU它们的规格参数相同,只是值不同。例如手机类型的常见属性有机身重量、屏幕尺寸、电池容量、运行内存等等。

 

表结构如下:

支持检索(search_type)、关联分类(catelog_id)、包含了基本属性以及销售属性(attr_type

 

商品属性分组表(pms_attr_group)

属性也有自己的分组,并且属性分组也是和三级分类关联的。例如手机类型的常见属性分组有主体、基本信息、存储、屏幕、电池、操作系统等等。

 

表结构如下:

关联分类(catelog_id

 

商品属性分组关联表(pms_attr_attrgroup_relation)

属性和属性分组是多对多的关系,所以会存在一个属性分组关联表来记录它们的关系。

 

表结构如下:

 

商品属性值表(pms_product_attr_value)

有了商品属性表以后,就需要有商品属性值表,SPU决定商品基本属性、SKU决定商品销售属性。商品属性表和分类关联,同一类产品的商品属性以及商品属性分组都一样,但它们的商品属性值不同。商品属性值与商品(SPU)相关联,不同的商品有不同的属性值,前面我们提到这就需要有一张商品属性值表,这里的attr_name是冗余字段。

 

表结构如下:

关联商品(spu_id

 

商品信息表(pms_spu_info)

商品信息表保存的是商品的基本信息,例如商品名称、商品介绍、发布状态等信息。这里商品重量和商品描述都属于商品的基本信息,与商品相关联,并且会常用到,故这里冗余了商品重量和商品介绍这两个字段。

 

表结构如下:

 

库存单位信息表(pms_sku_info)

库存信息表决定了商品的销售信息,例如价格、销量、库存量等信息,并且每一个SKU都有一个唯一ID来标识。

 

表结构如下: 唯一ID(sku_id)、关联商品(spu_id)、商品价格(price)、商品销量(sale_count

 

销售属性值表(pms_sku_sale_attr_value)

销售属性表决定了商品的售价,它同时关联了库存单位表(SKU)、商品属性表以及商品属性值表。

 

表结构如下: 关联的库存单位(sku_id)、商品属性(attr_id)、商品属性值(attr_value

 

初始化后台菜单

现在我们的后台管理系统商品服务模块只有分类管理、品牌管理两个菜单,我们接下来需要初始化一下菜单

 

谷粒商城课件里面有后台菜单的sql文件,我们使用sql文件生成菜单

 

需要注意的是提供的sys_menu.sql中使用的是mall_admin数据库,我们修改为gulimall_admin

 

运行后成功生成菜单

 

后续的开发方式

在配套课件中包含了已经对接好的前端页面源代码,同时谷粒商城中配套了很详细的接口文档,后面我们的开发方式只需要对照页面以及接口文档,开发各个模块所需接口即可。

 

谷粒商城在线接口文档地址

我们先完成商品系统模块,在课件中找到前端组件,将common以及product目录拷贝到前端工程的views/modules

商品服务模块

属性分组模块

属性分组列表

com.atguigu.gulimall.product.controller.AttrGroupController

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

        return R.ok().put("page", page);
    }

com.atguigu.gulimall.product.service.impl.AttrGroupServiceImpl

java
    @Override
    public PageUtils queryPage(Map<String, Object> params, Long catelogId) {

         LambdaQueryWrapper<AttrGroupEntity> queryWrapper = new LambdaQueryWrapper<>();

        queryWrapper.eq(catelogId>0,AttrGroupEntity::getCatelogId,catelogId);

        // queryWrapper.like(key!=null,AttrGroupEntity::getAttrGroupName,params.get("key")).or().like(key!=null,AttrGroupEntity::getAttrGroupId,key);

        String key = (String) params.get("key");
        if(key!=null){
            queryWrapper.and((obj) -> {
                obj.like(AttrGroupEntity::getAttrGroupName,params.get("key")).or().like(AttrGroupEntity::getAttrGroupId,key);
            });
        }
        IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),queryWrapper);
        return new PageUtils(page);
    }

需要注意的是,查询列表的先置条件是catelogId匹配上,然后再匹配key关键字是否与attrGroupName或者attrGroupId匹配,最开始我使用的第一种写法是错误的

错误写法:

java
queryWrapper.like(key!=null,AttrGroupEntity::getAttrGroupName,params.get("key")).or().like(key!=null,AttrGroupEntity::getAttrGroupId,key);

// 对应sql如下
//  SELECT attr_group_id,icon,catelog_id,sort,descript,attr_group_name FROM pms_attr_group WHERE (catelog_id = ? AND attr_group_name LIKE ? OR attr_group_id LIKE ?)  
// And后面的条件没有包裹住,
 

正确写法,将key的匹配作为一个整体条件

java
queryWrapper.and((obj) -> {
    obj.like(key!=null,AttrGroupEntity::getAttrGroupName,params.get("key")).or().like(key!=null,AttrGroupEntity::getAttrGroupId,key);
});

// 对应sql如下
// SELECT attr_group_id,icon,catelog_id,sort,descript,attr_group_name FROM pms_attr_group WHERE (catelog_id = ? AND ( (attr_group_name LIKE ? OR attr_group_id LIKE ?) ))

新增属性分组

新增属性分组选择所属分类这里发现最后一级空,导致无法选中,这里是因为前端组件只要识别到children字段,不管有没有数据都会加载出级联面板

 
 

解决方案很简单,只需要判断children数据为空数组的时候不返回该字段就行,这里需要用到@JsonInclude注解

 
  • ALWAYS 表示总是这个字段

  • NON_NULL 这个字段不为空的时候才响应包含字段

  • NOT_EMPTY 这个字段不为空的,包含判断空集合

com.atguigu.gulimall.product.dto.CategoryDto

java
package com.atguigu.gulimall.product.dto;

import com.atguigu.gulimall.product.entity.CategoryEntity;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Data;

import java.util.ArrayList;
import java.util.List;

@Data
public class CategoryDto extends CategoryEntity {

    // 扩展children字段,以便于保存子分类
    @JsonInclude(JsonInclude.Include.NON_EMPTY)
    private List<CategoryDto> children = new ArrayList<>();
}

可以看到已经成功生效,我们也可以正常选择商品分类

 

新增使用mybatiplus默认实现的接口即可

获取属性分组详情

mybatis-plus默认实现的获取属性分组详情接口是可用的,不过前端回显属性分类的时候需要后端接口返回当前分类id的完整路径,方便前端级联选择器回显数据。这里也可以前端实现,我们以后端练习为主。

 

com.atguigu.gulimall.product.controller.AttrGroupController

java
    /**
     * 信息
     */
    @RequestMapping("/info/{attrGroupId}")
    public R info(@PathVariable("attrGroupId") Long attrGroupId){
		AttrGroupEntity attrGroup = attrGroupService.getById(attrGroupId);

        // 根据当前商品分类id查询分类的完整路径,数组显示
        attrGroup.setCatelogPath(categoryService.findCatelogPath(attrGroup.getCatelogId()));

        return R.ok().put("attrGroup", attrGroup);
    }

com.atguigu.gulimall.product.service.CategoryService

java
Long[] findCatelogPath(Long catelogId);

com.atguigu.gulimall.product.service.impl.CategoryServiceImpl

java
     /**
     * 根据分类id查找完整路径
     *
     * @param catelogId
     * @return
     */
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        Long[] result = {};

        Long[] parentPath = getCatelogPathByCatelogId(catelogId,result);
        return parentPath;
    }

    /**
     * @param catelogId
     * @param result 最终结果
     * @return
     */
    public Long[] getCatelogPathByCatelogId(Long catelogId,Long[] result){

        List<Long> list = new ArrayList<>(Arrays.asList(result));

        LambdaQueryWrapper<CategoryEntity> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(CategoryEntity::getCatId,catelogId);
        CategoryEntity category = this.getById(catelogId);

        if(category != null){
            // 最后一条代表当前分类
            if(list.size() == 0){
                list.add(catelogId);
            }

            // 添加父级
            if(category.getParentCid() != 0){
                list.add(0,category.getParentCid());

                return getCatelogPathByCatelogId(category.getParentCid(),list.toArray(result));
            }else{
                // 返回最终结果
                return result;
            }
        }else{
            return null;
        }
    }
 

完善品牌管理模块

品牌列表分页功能

品牌管理模块目前查询条件还未生效,并且分页统计的共计条数也不对,接下来引入mybatis-plus分页插件

 

配置mybatis-plus分页插件 com.atguigu.gulimall.product.config.MybatisPlusConfig

java
package com.atguigu.gulimall.product.config;

import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
@MapperScan("com.atguigu.gulimall.product.dao")
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        paginationInterceptor.setOverflow(true);
        paginationInterceptor.setLimit(1000);
        return paginationInterceptor;
    }
}

配置成功以后发现可以正常返回数据总数了

 

品牌关联分类功能

 

点击关联分类按钮时会发起一个根据品牌id获取绑定的商品分类的接口

com.atguigu.gulimall.product.controller.CategoryBrandRelationController

java
/**
 * 品牌分类关联
 *
 * @author zhaochao
 * @email 1798231822@qq.com
 * @date 2023-05-13 20:36:01
 */
@RestController
@RequestMapping("product/categorybrandrelation")
public class CategoryBrandRelationController {
    @Autowired
    private CategoryBrandRelationService categoryBrandRelationService;

    @RequestMapping("/catelog/list")
    public R catelogList(@RequestParam("brandId")Long brandId){
        if(brandId==null){
            return R.error("brandId不存在");
        }
        return R.ok().put("data",categoryBrandRelationService.getCatelogListByBrandId(brandId));
    }

}

com.atguigu.gulimall.product.service.CategoryBrandRelationService

java
/**
 * 品牌分类关联
 *
 * @author zhaochao
 * @email 1798231822@qq.com
 * @date 2023-05-13 16:54:34
 */
public interface CategoryBrandRelationService extends IService<CategoryBrandRelationEntity> {
    List<CategoryBrandRelationEntity> getCatelogListByBrandId(Long brandId);
}

com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl

java
@Service("categoryBrandRelationService")
public class CategoryBrandRelationServiceImpl extends ServiceImpl<CategoryBrandRelationDao, CategoryBrandRelationEntity> implements CategoryBrandRelationService {

    @Override
    public List<CategoryBrandRelationEntity> getCatelogListByBrandId(Long brandId) {
        return this.list(new LambdaQueryWrapper<CategoryBrandRelationEntity>().eq(brandId!=null,CategoryBrandRelationEntity::getBrandId,brandId));
    }
}

根据品牌id获取关联的分类信息接口已经正常,但是我们发现添加关联分类后品牌名和分类名为空,原因是保存接口未写入。品牌分类关联表中分类名称和品牌名称都是冗余字段,我们在保存接口save的时候写入名称。

com.atguigu.gulimall.product.controller.CategoryBrandRelationController

java
    /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@RequestBody CategoryBrandRelationEntity categoryBrandRelation){

        CategoryBrandRelationEntity categoryBrandRelationEntity = categoryBrandRelation;

        CategoryEntity category = categoryService.getById(categoryBrandRelation.getCatelogId());
        BrandEntity brand = brandService.getById(categoryBrandRelation.getBrandId());

        categoryBrandRelationEntity.setBrandName(brand.getName());
        categoryBrandRelationEntity.setCatelogName(category.getName());

		categoryBrandRelationService.save(categoryBrandRelation);

        return R.ok();
    }

现在接口正常

 

现在这里依然存在一个问题,那就是我们的品牌名称以及分类名称都是从pms_category_brand_relation分类品牌关联表中查询出来的,其中brand_namecatelog_name都是冗余字段。如果有一天我们更新了品牌或者分类名称,那么这里关联表中的数据就是有问题了,所以我们在更新品牌和分类名称时也要同步更新关联表中存在并且对应的数据。

com.atguigu.gulimall.product.controller.BrandController

java
    /**
     * 修改
     */
    @RequestMapping("/update")
    public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
		brandService.updateDetails(brand);

        return R.ok();
    }

com.atguigu.gulimall.product.service.impl.BrandServiceImpl

java
@Service("brandService")
public class BrandServiceImpl extends ServiceImpl<BrandDao, BrandEntity> implements BrandService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Override
    @Transactional
    public void updateDetails(BrandEntity brand) {
        this.updateById(brand);

        //如果更新到品牌的名称,就需要同步更新
        if(!StringUtils.isEmpty(brand.getName())){
            // 同步更新品牌分类关联表中的数据
            categoryBrandRelationService.updateBrand(brand.getBrandId(),brand.getName());

            // TODO 更新其他关联
        }
    }

}

com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl

java
@Service("categoryBrandRelationService")
public class CategoryBrandRelationServiceImpl extends ServiceImpl<CategoryBrandRelationDao, CategoryBrandRelationEntity> implements CategoryBrandRelationService {

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryBrandRelationEntity> page = this.page(
                new Query<CategoryBrandRelationEntity>().getPage(params),
                new QueryWrapper<CategoryBrandRelationEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public List<CategoryBrandRelationEntity> getCatelogListByBrandId(Long brandId) {
        return this.list(new LambdaQueryWrapper<CategoryBrandRelationEntity>().eq(brandId!=null,CategoryBrandRelationEntity::getBrandId,brandId));
    }

    @Override
    public void updateBrand(Long brandId, String name) {
        CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
        categoryBrandRelationEntity.setBrandId(brandId);
        categoryBrandRelationEntity.setBrandName(name);
        this.update(categoryBrandRelationEntity,new UpdateWrapper<CategoryBrandRelationEntity>().eq("brand_id",brandId));
    }

}

修改分类名称同样也需要更新对应的关联表 com.atguigu.gulimall.product.controller.CategoryController

java
    /**
     * 修改
     */
    @RequestMapping("/update")
    public R update(@RequestBody CategoryEntity category){
        categoryService.updateDetails(category);

        return R.ok();
    }

com.atguigu.gulimall.product.service.impl.CategoryServiceImpl

java
    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    @Transactional
    @Override
    public void updateDetails(CategoryEntity category) {
        this.updateById(category);

        if(!StringUtils.isEmpty(category.getName())){
            // 同步更新关联表中的分类信息
            categoryBrandRelationService.updateCategory(category.getCatId(),category.getName());
        }
    }

com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl

java
    @Override
    public void updateCategory(Long catId, String name) {
        CategoryBrandRelationEntity categoryBrandRelationEntity = new CategoryBrandRelationEntity();
        categoryBrandRelationEntity.setCatelogId(catId);
        categoryBrandRelationEntity.setCatelogName(name);
        this.update(categoryBrandRelationEntity,new UpdateWrapper<CategoryBrandRelationEntity>().eq("catelog_id",catId));
    }

Transactional表示开启Mybatis-plus事务,会自动帮我们管理事务,开启之前需要在mybatis-plus配置文件上添加@EnableTransactionManagement注解

com.atguigu.gulimall.product.config

java
@Configuration
@EnableTransactionManagement
@MapperScan("com.atguigu.gulimall.product.dao")
public class MybatisPlusConfig {

    /**
     * 添加分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor(){
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        paginationInterceptor.setOverflow(true);
        paginationInterceptor.setLimit(1000);
        return paginationInterceptor;
    }
}

规格参数模块

属性分组模块还有一个关联的功能,作用是将属性分组和规格参数关联起来,所以我们需要先完成规格参数模块

新增规格参数

新增的规格参数需要与商品分类以及规格参数分类关联,目前新增的规格参数仅仅只是向pms_attr表中新增一条数据,除此之外我们还需要向pms_attr_attrgroup_relation规格参数分组关联表中也写入一条关联数据

 

新增规格参数的默认接收实体类是AttrEntity实体类,这里包含了一些额外参数,我们需要定义一个VO或者DTO来接收参数避免直接修改实体类,使代码混乱。

 

com.atguigu.gulimall.product.controller.AttrController

java
    /**
     * 保存
     */
    @RequestMapping("/save")
    public R save(@RequestBody AttrVo attr){
		attrService.saveAttr(attr);

        return R.ok();
    }

这里接收参数多了一个attrGroupId参数,原先的attrEntity实体类中没有这个参数,所以需要定义一个vo对象来接收参数

Java中Object的划分

 
 

com.atguigu.gulimall.product.vo.AttrVo

java
package com.atguigu.gulimall.product.vo;

import lombok.Data;

@Data
public class AttrVo {
    /**
     * 属性id
     */
    private Long attrId;
    /**
     * 属性名
     */
    private String attrName;
    /**
     * 是否需要检索[0-不需要,1-需要]
     */
    private Integer searchType;
    /**
     * 值类型[0-为单个值,1-可以选择多个值]
     */
    private Integer valueType;
    /**
     * 属性图标
     */
    private String icon;
    /**
     * 可选值列表[用逗号分隔]
     */
    private String valueSelect;
    /**
     * 属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]
     */
    private Integer attrType;
    /**
     * 启用状态[0 - 禁用,1 - 启用]
     */
    private Long enable;
    /**
     * 所属分类
     */
    private Long catelogId;
    /**
     * 快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整
     */
    private Integer showDesc;


    /**
     * 属性分组id
     */
    private Long attrGroupId;
}

com.atguigu.gulimall.product.service.AttrService

java
void saveAttr(AttrVo attr);

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java
@Service("attrService")
public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {

    @Autowired
    AttrAttrgroupRelationService attrAttrgroupRelationService;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<AttrEntity> page = this.page(
                new Query<AttrEntity>().getPage(params),
                new QueryWrapper<AttrEntity>()
        );

        return new PageUtils(page);
    }

    @Transactional
    @Override
    public void saveAttr(AttrVo attr) {
        AttrEntity attrEntity = new AttrEntity();
        BeanUtils.copyProperties(attr,attrEntity);
        this.save(attrEntity);

        AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
        attrAttrgroupRelationEntity.setAttrId(attrEntity.getAttrId());
        attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());
        // 同时向属性分组关系表中插入一条关联数据
        attrAttrgroupRelationService.save(attrAttrgroupRelationEntity);
    }

}

规格参数列表

com.atguigu.gulimall.product.controller.AttrController

java
    /**
     * 列表
     */
    @RequestMapping("/base/list/{catelogId}")
    public R baseList(@RequestParam Map<String, Object> params,@PathVariable Long catelogId){
        PageUtils page = attrService.getBaseList(params,catelogId);

        return R.ok().put("page", page);
    }

com.atguigu.gulimall.product.service.AttrService

java
    PageUtils getBaseList(Map<String, Object> params,Long catelogId);

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java

    @Autowired
    AttrAttrgroupRelationService attrAttrgroupRelationService;

    @Autowired
    CategoryService categoryService;

    @Autowired
    AttrGroupService attrGroupService;

    @Override
    public PageUtils getBaseList(Map<String, Object> params, Long catelogId) {
        // 最终返回的结果
        List<AttrResVo> attrResVoList = new ArrayList<>();

        LambdaQueryWrapper<AttrEntity> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(catelogId>0,AttrEntity::getCatelogId,catelogId);


        // 检索的关键字
        String key = (String) params.get("key");
        if(key !=null){
            queryWrapper.and(obj -> {
               obj.like(AttrEntity::getAttrName,key).or().like(AttrEntity::getAttrId,key);
            });
        }

        IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),queryWrapper);
        PageUtils pageUtils = new PageUtils(page);

        List<AttrEntity> records = page.getRecords();

        // 循环遍历,设置categoryNames和groupName
        records.stream().map((attrEntity) -> {

            AttrResVo attrResVo = new AttrResVo();
            BeanUtils.copyProperties(attrEntity,attrResVo);

            List<AttrGroupEntity> groupEntityList = new ArrayList<>();
            // 查询分组名称
            List<String> categoryNames = categoryService.findCatelogNamePath(attrResVo.getCatelogId());
            attrResVo.setCatelogName(String.join("/",categoryNames));

            // 查询分类名称,传入catelogId,查询名称,需要注意的是属性和属性分组是多对多的关联关系
            List<AttrAttrgroupRelationEntity> list = attrAttrgroupRelationService.list(new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId,attrEntity.getAttrId()));

            List<Long> ids = list.stream().map( attrAttrgroupRelationEntity1 -> attrAttrgroupRelationEntity1.getAttrGroupId()).collect(Collectors.toList());

            List<AttrGroupEntity> groupEntityList1 = attrGroupService.getBaseMapper().selectList(new LambdaQueryWrapper<AttrGroupEntity>().in(AttrGroupEntity::getAttrGroupId,ids));
            // 拼接groupNames,groupName可能存在多个
            List<String> groupNamesList = groupEntityList1.stream().map( attrGroupEntity1 -> attrGroupEntity1.getAttrGroupName ()).collect(Collectors.toList());
            attrResVo.setGroupName(String.join("/",groupNamesList));

            attrResVoList.add(attrResVo);

            return attrEntity;
        }).collect(Collectors.toList());

        pageUtils.setList(attrResVoList);
        return pageUtils;
    }

com.atguigu.gulimall.product.service.impl.CategoryServiceImpl

java
    /**
     * 根据分类id查找完整路径
     *
     * @param catelogId
     * @return
     */
    @Override
    public List<String> findCatelogNamePath(Long catelogId) {

        List<String> result = getCatelogNamePathByCatelogId(catelogId,new ArrayList<>());

        return result;
    }

    /**
     * 传入分类id 查询分类名称拼接成的path
     * @param catelogId
     * @param result
     * @return
     */
    public List<String> getCatelogNamePathByCatelogId(Long catelogId,List<String> result){

        List<String> list = result;

        LambdaQueryWrapper<CategoryEntity> queryWrapper = new LambdaQueryWrapper();
        queryWrapper.eq(CategoryEntity::getCatId,catelogId);
        CategoryEntity category = this.getById(catelogId);

        if(category != null){
            list.add(category.getName());

            // 添加父级
            if(category.getParentCid() != 0){
                return getCatelogNamePathByCatelogId(category.getParentCid(),result);
            }else{
                // 返回最终结果
                return list;
            }
        }else{
            return null;
        }
    }

规格详情

com.atguigu.gulimall.product.controller.AttrController

java
    /**
     * 信息
     */
    @RequestMapping("/info/{attrId}")
    public R info(@PathVariable("attrId") Long attrId){
        AttrResVo attr = attrService.getInfo(attrId);

        return R.ok().put("attr", attr);
    }

com.atguigu.gulimall.product.service.AttrService

java
AttrResVo getInfo(Long attrId);

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java
    @Override
    public AttrResVo getInfo(Long attrId) {
        AttrEntity attrEntity = this.getById(attrId);
        AttrResVo attrResVo = new AttrResVo();
        BeanUtils.copyProperties(attrEntity,attrResVo);

        // 查找分类Id
        Long[] catelogPath = categoryService.findCatelogPath(attrResVo.getCatelogId());
        attrResVo.setCatelogPath(catelogPath);

        // 查找属性分组关联表
        AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = attrAttrgroupRelationService.getBaseMapper().selectOne(new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId,attrResVo.getAttrId()));
        attrResVo.setAttrGroupId(attrAttrgroupRelationEntity.getAttrGroupId());

        return attrResVo;
    }

删除规格

com.atguigu.gulimall.product.controller.AttrController

java
    /**
     * 删除
     */
    @RequestMapping("/delete")
    public R delete(@RequestBody Long[] attrIds){
		attrService.removeByIds(Arrays.asList(attrIds));

        List<Long> list = Arrays.asList(attrIds);
        attrService.deleteWithGroup(list.get(0));
        return R.ok();
    }

com.atguigu.gulimall.product.service.AttrService

java
void deleteWithGroup(Long attrId);

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java
    @Transactional
    @Override
    public void deleteWithGroup(Long attrId) {
        this.baseMapper.deleteById(attrId);

        // 删除关联关系
        attrAttrgroupRelationService.remove(new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId,attrId));
    }

销售属性模块

销售属性列表和基本属性列表查询的表是同一个,唯一的区别是AttrType一个是1,一个是0对于我们业务中的常量,最好是将其封装为枚举类型,提升代码的可读性。

定义枚举类

com.atguigu.common.constant.AttrEnum

java
package com.atguigu.common.constant;

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

}

com.atguigu.gulimall.product.controller.AttrController

java
    /**
     * 列表
     */
    @RequestMapping("/{type}/list/{catelogId}")
    public R baseList(@RequestParam Map<String, Object> params,@PathVariable Long catelogId,@PathVariable String type){
        PageUtils page = attrService.getBaseList(params,catelogId,type);

        return R.ok().put("page", page);
    }

com.atguigu.gulimall.product.service.AttrService

java
PageUtils getBaseList(Map<String, Object> params,Long catelogId,String type);

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java
    @Override
    public PageUtils getBaseList(Map<String, Object> params, Long catelogId,String type) {
        // 最终返回的结果
        List<AttrResVo> attrResVoList = new ArrayList<>();

        LambdaQueryWrapper<AttrEntity> queryWrapper = new LambdaQueryWrapper<AttrEntity>();
        queryWrapper.eq(catelogId>0,AttrEntity::getCatelogId,catelogId);
        queryWrapper.eq(AttrEntity::getAttrType,"base".equalsIgnoreCase(type)?AttrEnum.ATTR_TYPE_BASE.getCode() :AttrEnum.ATTR_TYPE_SALE.getCode());

        // 检索的关键字
        String key = (String) params.get("key");
        if(key !=null){
            queryWrapper.and(obj -> {
               obj.like(AttrEntity::getAttrName,key).or().like(AttrEntity::getAttrId,key);
            });
        }

        IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),queryWrapper);
        PageUtils pageUtils = new PageUtils(page);

        List<AttrEntity> records = page.getRecords();

        // 循环遍历,设置categoryNames和groupName
        records.stream().map((attrEntity) -> {

            AttrResVo attrResVo = new AttrResVo();
            BeanUtils.copyProperties(attrEntity,attrResVo);

            List<AttrGroupEntity> groupEntityList = new ArrayList<>();
            // 查询分组名称
            List<String> categoryNames = categoryService.findCatelogNamePath(attrResVo.getCatelogId());
            if(categoryNames!=null){
                attrResVo.setCatelogName(String.join("/",categoryNames));
            }

            // 查询分类名称,传入catelogId,查询名称,需要注意的是属性和属性分组是多对多的关联关系
            List<AttrAttrgroupRelationEntity> list = attrAttrgroupRelationService.list(new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId,attrEntity.getAttrId()));

            List<Long> ids = list.stream().map( attrAttrgroupRelationEntity1 -> attrAttrgroupRelationEntity1.getAttrGroupId()).collect(Collectors.toList());

            if(ids.size()>0){
                List<AttrGroupEntity> groupEntityList1 = attrGroupService.getBaseMapper().selectList(new LambdaQueryWrapper<AttrGroupEntity>().in(AttrGroupEntity::getAttrGroupId,ids));
                // 拼接groupNames,groupName可能存在多个
                List<String> groupNamesList = groupEntityList1.stream().map( attrGroupEntity1 -> attrGroupEntity1.getAttrGroupName ()).collect(Collectors.toList());
                attrResVo.setGroupName(String.join("/",groupNamesList));
            }
            attrResVoList.add(attrResVo);

            return attrEntity;
        }).collect(Collectors.toList());

        pageUtils.setList(attrResVoList);
        return pageUtils;
    }

同时在前面新增销售属性接口,当我们新增的是销售属性的时候无需绑定属性分组,所以需要判断下,如果是销售属性就不需要操作属性分组表

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java
    @Transactional
    @Override
    public void saveAttr(AttrVo attr) {
        AttrEntity attrEntity = new AttrEntity();
        BeanUtils.copyProperties(attr,attrEntity);
        this.save(attrEntity);

        // 保存的时候销售属性不需要属性分组中
        if(attrEntity.getAttrType() == AttrEnum.ATTR_TYPE_BASE.getCode()){
            AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
            attrAttrgroupRelationEntity.setAttrId(attrEntity.getAttrId());
            attrAttrgroupRelationEntity.setAttrGroupId(attr.getAttrGroupId());
            // 同时向属性分组关系表中插入一条关联数据
            attrAttrgroupRelationService.save(attrAttrgroupRelationEntity);
        }
    }

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java
    @Override
    public AttrResVo getInfo(Long attrId) {
        AttrEntity attrEntity = this.getById(attrId);
        AttrResVo attrResVo = new AttrResVo();
        BeanUtils.copyProperties(attrEntity,attrResVo);

        // 查找分类Id
        Long[] catelogPath = categoryService.findCatelogPath(attrResVo.getCatelogId());
        attrResVo.setCatelogPath(catelogPath);

        if(attrEntity.getAttrType() == AttrEnum.ATTR_TYPE_BASE.getCode()){
            // 查找属性分组关联表
            AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = attrAttrgroupRelationService.getBaseMapper().selectOne(new LambdaQueryWrapper<AttrAttrgroupRelationEntity>().eq(AttrAttrgroupRelationEntity::getAttrId,attrResVo.getAttrId()));
            attrResVo.setAttrGroupId(attrAttrgroupRelationEntity.getAttrGroupId());
        }

        return attrResVo;
    }

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java
    @Override
    public void updateWithGroup(AttrVo attrVo) {
        // 更新属性信息
        AttrEntity attrEntity = new AttrEntity();
        BeanUtils.copyProperties(attrVo,attrEntity);
        this.updateById(attrEntity);

        // 更新关联的信息
        if(attrEntity.getAttrType()==AttrEnum.ATTR_TYPE_BASE.getCode()){
            AttrAttrgroupRelationEntity attrAttrgroupRelationEntity = new AttrAttrgroupRelationEntity();
            LambdaUpdateWrapper<AttrAttrgroupRelationEntity> lambdaUpdateWrapper = new LambdaUpdateWrapper<>();
            attrAttrgroupRelationEntity.setAttrId(attrVo.getAttrId());
            attrAttrgroupRelationEntity.setAttrGroupId(attrVo.getAttrGroupId());
            attrAttrgroupRelationService.update(attrAttrgroupRelationEntity,lambdaUpdateWrapper);
        }
    }

属性分组模块

点击关联可以针对该分组进行关联属性的管理

获取关联的属性列表

com.atguigu.gulimall.product.controller.AttrGroupController

java
    /**
     * 根据分组查询关联的属性
     */
    @RequestMapping("/{attrgroupId}/attr/relation")
    public R getAttrByGroupId(@PathVariable("attrgroupId") Long attrgroupId){

        List<AttrEntity> attrEntityList = attrService.getAttrByGroupId(attrgroupId);

        return R.ok().put("data",attrEntityList);
    }

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java
    /**
     * 根据分组ID查询属性列表
     * @param attrgroupId
     * @return
     */
    @Override
    public List<AttrEntity> getAttrByGroupId(Long attrgroupId) {
        LambdaQueryWrapper<AttrAttrgroupRelationEntity> queryWrapper = new LambdaQueryWrapper<>();
        List<AttrEntity> attrEntityList = new ArrayList<>();
        if(attrgroupId!=null){
            queryWrapper.eq(AttrAttrgroupRelationEntity::getAttrGroupId,attrgroupId);
            // 查询出关联的属性id集合
            List<Long> attrIds = attrAttrgroupRelationService.list(queryWrapper).stream().map(item -> item.getAttrId()).collect(Collectors.toList());
            if(attrIds!=null && attrIds.size()!=0){
                // 使用in查询attrIds集合
                attrEntityList = this.list(new LambdaQueryWrapper<AttrEntity>().in(AttrEntity::getAttrId,attrIds));
            }
        }

        return attrEntityList;
    }

批量删除数据

com.atguigu.gulimall.product.controller.AttrGroupController

java
    /**
     * 根据属性id和分组id批量删除数据
     * @param attrGroupRelationVos
     * @return
     */
    @PostMapping("/attr/relation/delete")
    public R deleteBatch(@RequestBody AttrGroupRelationVo[] attrGroupRelationVos){

        attrService.deleteBatch(attrGroupRelationVos);

        return R.ok();
    }

com.atguigu.gulimall.product.service.impl.AttrServiceImpl

java
    /**
     * 传入attrId和attrGroupId数组批量删除
     * @param attrGroupRelationVos
     */
    @Override
    public void deleteBatch(AttrGroupRelationVo[] attrGroupRelationVos) {
        List<AttrAttrgroupRelationEntity> entities = Arrays.asList(attrGroupRelationVos).stream().map(item -> {
            AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
            BeanUtils.copyProperties(item,relationEntity);
            return relationEntity;
        }).collect(Collectors.toList());
        attrAttrgroupRelationDao.deleteBatchRelation(entities);
    }

批量添加属性分组

com.atguigu.gulimall.product.controller.AttrGroupController

java
    /**
     * 添加属性与分组关联关系
     */
    @PostMapping("/attr/relation")
    public R relation(@RequestBody List<AttrAttrgroupRelationEntity> attrAttrgroupRelationEntity){

        attrAttrgroupRelationService.relation(attrAttrgroupRelationEntity);
        return R.ok();
    }

com.atguigu.gulimall.product.service.impl.AttrAttrgroupRelationServiceImpl

java
    public void relation(List<AttrAttrgroupRelationEntity> attrAttrgroupRelationEntity) {
        this.saveBatch(attrAttrgroupRelationEntity);
    }

商品管理模块

发布商品

发布商品页面会调用获取会员等级列表的接口,所以需要先添加该接口,目前改接口404,是因为网关服务还未添加对应的配置

gulimall-gateway/src/main/resources/application.yml

yml
spring:
  cloud:
    gateway:
      routes:

        # 会员服务
        - id: member_route
          uri: lb://gulimall-member
          predicates:
             - Path=/api/member/**
          filters:
             - RewritePath=/api/(?<segment>.*),/$\{segment}

发布商品页西选择分类后会调用一个根据分类获取品牌的接口,目前还没有编写

com.atguigu.gulimall.product.controller.CategoryBrandRelationController

java
    /**
     * 根据分类id查询品牌列表
     */
    @RequestMapping("/brands/list")
    public R brandsList(@RequestParam("catId") String catId){
        List<BrandDto> brandEntityList = categoryBrandRelationService.getBrandByCatId(catId);
        return R.ok().put("data",brandEntityList);
    }

com.atguigu.gulimall.product.service.impl.CategoryBrandRelationServiceImpl

java
    @Override
    public List<BrandDto> getBrandByCatId(String catId) {
        List<BrandDto> brandEntities = new ArrayList<>();
        /**
         * 根据catId查询分类品牌关联表,然后返回品牌信息
         */
        List<CategoryBrandRelationEntity> categoryBrandRelationEntities = this.list(new LambdaQueryWrapper<CategoryBrandRelationEntity>().eq(CategoryBrandRelationEntity::getCatelogId, catId));
        if(categoryBrandRelationEntities!=null && categoryBrandRelationEntities.size()>0){
            for (CategoryBrandRelationEntity categoryBrandRelationEntity : categoryBrandRelationEntities) {
                BrandDto brandEntity = new BrandDto();
                brandEntity.setBrandId(categoryBrandRelationEntity.getBrandId());
                brandEntity.setBrandName(categoryBrandRelationEntity.getBrandName());
                brandEntities.add(brandEntity);
            }
        }
        return brandEntities;
    }

上次更新于: