零、属性分组
0.1 属性分组查询全部增加模糊查询

修改gulimallproduct/attrgroup/list/{catelogId}中service层的逻辑
@Overridepublic PageUtils queryPage(Map<String, Object> params, Long catelogId) {// 一开始就判断是否进行模糊查询,修改wrapper// 前端提交的时候还会提交一个参数名,这个参数名是一个模糊查询,可以匹配任意分组字段,所以需要判断String key = (String) params.get("key");// 这里我们假设模糊查询针对name跟id// select * from pms_attr_group where catelog_id = ? and (attr_group_id = key or attr_group_name = key)QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();if (!StringUtils.isEmpty(key)) {wrapper.and((obj)->{obj.eq("attr_group_id", key).or().like("attr_group_name", key);});}// 前端规定如果传过来的catelogId为0,那么表示没有选中三级分类,则查询所有属性分组if (catelogId == 0) {IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params),wrapper);return new PageUtils(page);}else {// 当我们需要查询特定目录的分组属性时,再设定按ID查询wrapper.eq("catelog_id", catelogId);IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);return new PageUtils(page);}}
0.2 属性分组与属性建立关联
0.3 规格参数新增与VO
我们先来看规格参数,当我们点击平台属性/规格参数时,浏览器会发送请求:
http://localhost:88/api/product/attr/base/list/0?t=1646887422208&page=1&limit=10&key=获取基础属性的列表。
0.3.1 新增规格参数


发送的保存请求中,携带了属性分组的ID,但是AttrEntity实体类中并没有AttrGroupId。而AttrGroupId是存储在pms_attr_attrgroup_relation表中的,所以在进行存储的时候,同时需要更新pms_attr_attrgroup_relation表。
0.3.2 vo
当有新增字段时,我们往往会在entity实体类中新建一个字段,并标注数据库中不存在该字段,然而这种方式并不规范。
所以引入vo,那什么是vo呢?在java中,涉及到了这几种类型:
- PO(persistant object) 持久对象:PO 就是对应数据库中某个表中的一条记录,多个记录可以用 PO 的集合。 PO 中应该不包含任何对数据库的操作。
- DO(Domain Object)领域对象:就是从现实世界中抽象出来的有形或无形的业务实体。
- TO(Transfer Object) ,数据传输对象:不同的应用程序之间传输的对象
- DTO(Data Transfer Object)数据传输对象:这个概念来源于 J2EE 的设计模式,原来的目的是为了 EJB 的分布式应用提供粗粒度的数据实体,以减少分布式调用的次数,从而提高分布式调用的性能和降低网络负载,但在这里,泛指用于展示层与服务层之间的数据传输对象。
- VO(value object) 值对象:通常用于业务层之间的数据传递,和 PO 一样也是仅仅包含数据而已。但应是抽象出的业务对象 , 可以和表对应 , 也可以不 , 这根据业务的需要 。用 new 关键字创建,由GC 回收的。
- 可以也称为View object:视图对象;接受页面传递来的数据,封装对象将业务处理完成的对象,封装成页面要用的数据
- BO(business object) 业务对象:从业务模型的角度看 , 见 UML 元件领域模型中的领域对象。封装业务逻辑的 java 对象 , 通过调用 DAO 方法 , 结合 PO,VO 进行业务操作。business object: 业务对象 主要作用是把业务逻辑封装为一个对象。这个对象可以包括一个或多个其它的对象。 比如一个简历,有教育经历、工作经历、社会关系等等。 我们可以把教育经历对应一个 PO ,工作经历对应一个 PO ,社会关系对应一个 PO 。 建立一个对应简历的 BO 对象处理简历,每个 BO 包含这些 PO 。 这样处理业务逻辑时,我们就可以针对 BO 去处理。
- POJO(plain ordinary java object) 简单无规则 java 对象:传统意义的 java 对象。就是说在一些 Object/Relation Mapping 工具中,能够做到维护数据库表记录的 persisent object 完全是一个符合 Java Bean 规范的纯 Java 对象,没有增加别的属性和方法。我的理解就是最基本的 java Bean ,只有属性字段及 setter 和 getter方法!。POJO 是 DO/DTO/BO/VO 的统称。
- DAO(data access object) 数据访问对象:是一个 sun 的一个标准 j2ee 设计模式, 这个模式中有个接口就是 DAO ,它负持久层的操作。为业务层提供接口。此对象用于访问数据库。通常和 PO 结合使用, DAO 中包含了各种数据库的操作方法。通过它的方法 , 结合 PO 对数据库进行相关的操作。夹在业务逻辑与数据库资源中间。配合 VO, 提供数据库的 CRUD 操作。
vo也称为View object:视图对象;接受页面传递来的数据,封装对象将业务处理完成的对象,封装成页面要用的数据。
package com.atguigu.gulimall.gulimallproduct.vo;import com.baomidou.mybatisplus.annotation.TableId;import lombok.Data;@Datapublic 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;}
0.3.3 保存属性基本信息与关联关系
修改AttrController中的/save
@RequestMapping("/save")//@RequiresPermissions("gulimallproduct:attr:save")public R save(@RequestBody AttrVo attr){ // 这里AttrEntity改成AttrVo,可以收集到AttrGroupId// attrService.save(attr);attrService.saveAttr(attr); // 这个方法会存储属性分组IDreturn R.ok();}
AttrService生命,AttrServiceImpl实现
@AutowiredAttrAttrgroupRelationDao relationDao;@Overridepublic void saveAttr(AttrVo attr) {// 1. 先在`pms_attr`表中保存属性的基本信息AttrEntity attrEntity = new AttrEntity();// org.springframework.beans.BeanUtils的copyProperties可以将前一个对象的属性直接拷贝给后面的对象// 前提是属性名必须一致BeanUtils.copyProperties(attr, attrEntity);// 保存基本数据this.save(attrEntity);// 2. 在`pms_attr_attrgroup_relation`表中,保存属性与属性分组的关联关系AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();relationEntity.setAttrId(attrEntity.getAttrId());relationEntity.setAttrGroupId(attrVo.getAttrGroupId());relationDao.insert(relationEntity);}
0.3.4 查询规格参数列表
打开规格参数的时候会发送一个查询请求:http://localhost:88/api/gulimallproduct/attr/base/list/{catelogId}?t=1646896048461&page=1&limit=10&key=
在AttrController中添加对应方法:
// /base/list/{catelogId}@GetMapping("/base/list/{catelogId}")public R baseAttrList(@RequestParam Map<String, Object> params,@PathVariable("catelogId") Long catelogId) {PageUtils page = attrService.queryBaseAttrPage(params, catelogId);return R.ok().put("page", page);}
AttrService声明,AttrServiceImpl实现,同样需要考虑到模糊查询
@Overridepublic PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {QueryWrapper<AttrEntity> wrapper = new QueryWrapper<>();// 模糊查询// 无论查全部还是查具体目录,都检查是否进行模糊查询String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.and((obj)->{obj.eq("attr_id", key).or().like("attr_name", key);});}// 根據不同情況封裝不同條件 如果catelogId不是0,在表示查询某个具体目录的属性if (catelogId != 0) {wrapper.eq("catelog_id", catelogId);}IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),wrapper);return new PageUtils(page);}
成功查询属性列表,但是发现这里还需要显示所属分类(catelogName)和所属分组(attrGroupName)。开发文档,响应数据
0.3.5 显示所属分类和所属分组
新建OV
@Datapublic class AttrResponseVo extends AttrVo {//所属分类名字private String catelogName;//所属分组名字private String groupName;}
修改AttrServiceImpl中的queryBaseAttrPage方法
@Overridepublic PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {/*********** 根据catelogId查询出当前目录所对应的所有属性 *************/QueryWrapper<AttrEntity> wrapper = new QueryWrapper<>();// 模糊查询// 无论查全部还是查具体目录,都检查是否进行模糊查询String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.and((obj) -> {obj.eq("attr_id", key).or().like("attr_name", key);});}// 根據不同情況封裝不同條件 如果catelogId不是0,在表示查询某个具体目录的属性,设置条件查找// 否则就是查询所有属性列表if (catelogId != 0) {wrapper.eq("catelog_id", catelogId);}IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),wrapper);PageUtils pageUtils = new PageUtils(page);/*********** 查询上面查找出来的属性所对应的属性分组名和分类名 *************/// 从page中得到获取的记录——当前分类对应的所有属性List<AttrEntity> records = page.getRecords();List<AttrResponseVo> responseVoList = records.stream().map((attrEntity) -> {AttrResponseVo responseVo = new AttrResponseVo();BeanUtils.copyProperties(attrEntity, responseVo);// 在responseVo中设置分类和分组的名字// 1. 设置属性分组名// 先通过attrEntity中的attr_id在`pms_attr_attrgroup_relation`表中找到属性跟分组的记录// TODO 一个属性可能对应多个分组AttrAttrgroupRelationEntity relationEntity =relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));if (relationEntity != null) { // 这里需要判断一下是否为空,因为属性不一定跟属性分组进行了关联// 找到记录后获取属性分组IDLong attrGroupId = relationEntity.getAttrGroupId();// 然后再根据属性分组Id到属性分组表`pms_attr_group`中查找分组名AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrGroupId);responseVo.setGroupName(attrGroupEntity.getAttrGroupName()); // 设置属性分组名}// 2. 设置分类名// 通过responseVo/attrEntity中的catelog_id直接在`pms_category`表中找到对应的目录记录// 这个catelog_id在`pms_category`表中是cat_idCategoryEntity categoryEntity = categoryDao.selectById(responseVo.getCatelogId());if (categoryEntity != null) {responseVo.setCatelogName(categoryEntity.getName()); // 设置分类名}return responseVo;}).collect(Collectors.toList());pageUtils.setList(responseVoList); // 将处理好的结果集放到pageUtils中return pageUtils;}
一、规格参数属性修改与回显
1.1 回显

编写查询属性详情接口 查询属性详情文档
给AttrResponseVo加上catelogPath属性,用来记录分类完整路径。
修改/info/{attrId}接口
@RequestMapping("/info/{attrId}")//@RequiresPermissions("gulimallproduct:attr:info")public R info(@PathVariable("attrId") Long attrId){// AttrEntity attr = attrService.getById(attrId);AttrResponseVo respVo = attrService.getAttrInfo(attrId);return R.ok().put("attr", respVo);}
AtrrService中声明getAttrInfo方法,实现类实现方法
/*** 数据回显,并获取分类完整路径和分组信息* @param attrId* @return*/@Overridepublic AttrResponseVo getAttrInfo(Long attrId) {AttrEntity attrEntity = this.getById(attrId);AttrResponseVo responseVo = new AttrResponseVo();BeanUtils.copyProperties(attrEntity, responseVo);// 先查出当前attrId对应的属性与属性分组的关联信息// 1.设置属性分组信息AttrAttrgroupRelationEntity attrgroupRelationEntity = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));if (attrgroupRelationEntity != null) { // 属性不一定与分组相关联了,所以查询到的记录可能为nullresponseVo.setAttrGroupId(attrgroupRelationEntity.getAttrGroupId());// 通过关联信息中的属性分组Id获取属性分组,然后获取分组名AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelationEntity.getAttrGroupId());if (attrGroupEntity != null) {responseVo.setGroupName(attrGroupEntity.getAttrGroupName());}}// 2.设置分类信息Long catelogId = attrEntity.getCatelogId(); // 获取当前属性实体类中的分类Id// 通过分类Id查询完整路径,这个方法我们之前已经写过了,直接使用categoryService即可Long[] catelogPath = categoryService.findCatelogPath(catelogId);responseVo.setCatelogPath(catelogPath);// CategoryEntity categoryEntity1 = categoryService.getById(catelogId);CategoryEntity categoryEntity = categoryDao.selectById(catelogId);if (categoryEntity != null) {responseVo.setCatelogName(categoryEntity.getName());}return responseVo;}
1.2 属性修改
在修改的时候发现点击确定后并没有修改成功。因为我们修改发送的是/attr/update请求,而/update方法只是更新了pms_attr表,并没有更新pms_attr_attrgroup_relation表。所以需要修改/update方法
@RequestMapping("/update")//@RequiresPermissions("gulimallproduct:attr:update")public R update(@RequestBody AttrVo attr){// attrService.updateById(attr);attrService.updateAttr(attr);return R.ok();}
AtrrService中声明updateAttr方法,实现类实现方法
/*** 更新属性,同时更新属性与属性分组关联表* @param attr 前端提交的数据*/@Transactional@Overridepublic void updateAttr(AttrVo attr) {AttrEntity attrEntity = new AttrEntity();BeanUtils.copyProperties(attr, attrEntity);// 修改基本数据this.updateById(attrEntity);// 修改属性与属性关联表`pms_attr_attrgroup_relation`中的数据// UPDATE `pms_attr_attrgroup_relation` SET `attr_group_id` = ? WHERE `attr_id` = ?// todo 这里一个属性对应多个分组的情况没有考虑AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();relationEntity.setAttrGroupId(attr.getAttrGroupId());relationEntity.setAttrId(attr.getAttrId());// 查看属性和属性分组关联表中有没有attr_id对应的记录// 因为暂时是一条属性对应一个属性分组,所以可以用这种判断方法,如果是一个属性对应多个分组,则不行// todo 这里一个属性对应多个分组的情况没有考虑Long count = relationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));if (count > 0) {relationDao.update(relationEntity, new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));} else {relationDao.insert(relationEntity);}}
二、销售属性CRUD
在销售属性刷新页面,会向后台发送以下请求:
http://localhost:88/api/gulimallproduct/attr/sale/list/0?t=1646917480411&page=1&limit=10&key=/gulimallproduct/attr/sale/list/{catelogId}
销售属性的获取跟规格参数其实是一样的,因为所有的属性都放在pms_attr表中,是根据attr_type字段来区分是销售属性还是规格参数。(0表示销售属性,1表示规格参数,2两者都是(不启用))
2.1 获取分类销售属性
修改AttrController,查询参数规格属性和销售属性共用一个接口,所以直接在原来的方法上进行修改。
/*** 规格参数基本属性列表 以及 销售属性共用一个方法* @param params* @param catelogId* @return*/// /base/list/{catelogId}// /sale/list/{catelogId}// 添加attrType获取前端url是base还是sale以区分销售属性跟规格参数属性@GetMapping("/{attrType}/list/{catelogId}")public R baseAttrList(@RequestParam Map<String, Object> params,@PathVariable("catelogId") Long catelogId,@PathVariable("attrType") String attrType) {PageUtils page = attrService.queryBaseAttrPage(params, catelogId, attrType);return R.ok().put("page", page);}
同样,service修改声明,serviceImp修改l实现。AttrServiceImpl仅需要添加一个条件查找
/*** 查询规格参数属性/销售属性列表,模糊查询,同时显示属性的属性分组名称和分类名称** @param params* @param catelogId* @param attrType* @return*/@Overridepublic PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String attrType) {/*********** 根据catelogId查询出当前目录所对应的所有属性 *************/// 这里在查询销售属性的时候还需要判断一下attr_type条件,如果传入的attrType参数是base则表示查找参数规格的基本属性// 如果是sale,则表示查找销售属性QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("attr_type", "base".equals(attrType) ? 1 : 0);// 模糊查询// 无论查全部还是查具体目录,都检查是否进行模糊查询String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.and((obj) -> {obj.eq("attr_id", key).or().like("attr_name", key);});}// 根據不同情況封裝不同條件 如果catelogId不是0,在表示查询某个具体目录的属性,设置条件查找,反之就是查询所有属性列表if (catelogId != 0) {wrapper.eq("catelog_id", catelogId);}IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),wrapper);PageUtils pageUtils = new PageUtils(page);/*********** 查询上面查找出来的属性所对应的属性分组名和分类名 *************/// 从page中得到获取的记录——当前分类对应的所有属性List<AttrEntity> records = page.getRecords();List<AttrResponseVo> responseVoList = records.stream().map((attrEntity) -> {AttrResponseVo responseVo = new AttrResponseVo();BeanUtils.copyProperties(attrEntity, responseVo);// 在responseVo中设置分类和分组的名字// 1. 设置属性分组名// 先通过attrEntity中的attr_id在`pms_attr_attrgroup_relation`表中找到属性跟分组的记录// TODO 一个属性可能对应多个分组AttrAttrgroupRelationEntity relationEntity =relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));if (relationEntity != null) { // 这里需要判断一下是否为空,因为属性不一定跟属性分组进行了关联// 找到记录后获取属性分组IDLong attrGroupId = relationEntity.getAttrGroupId();// 然后再根据属性分组Id到属性分组表`pms_attr_group`中查找分组名AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrGroupId);responseVo.setGroupName(attrGroupEntity.getAttrGroupName()); // 设置属性分组名}// 2. 设置分类名// 通过responseVo/attrEntity中的catelog_id直接在`pms_category`表中找到对应的目录记录// 这个catelog_id在`pms_category`表中是cat_idCategoryEntity categoryEntity = categoryDao.selectById(responseVo.getCatelogId());if (categoryEntity != null) {responseVo.setCatelogName(categoryEntity.getName()); // 设置分类名}return responseVo;}).collect(Collectors.toList());pageUtils.setList(responseVoList); // 将处理好的结果集放到pageUtils中return pageUtils;}
2.2 新增分类销售属性
点击新增后,数据库中有数据但是没有在销售属性中展示。检查后端输出日志,发现报错空指针异常:
原因是销售属性并不存在属性分组。所以在设置分组信息时,需要进行一个判断。(最终代码直接放后面了,这里要改的太多了)
添加销售属性时pms_attr_attrgroup_relation,会出现一条没有attr_group_id的记录,这是因为共用了规格参数属性的接口,因此需要加一个判断。
由于经常需要判断attrType,直接在common中定义一个常量类
package com.atguigu.common.constant;/*** 常量类* @author mrlinxi* @create 2022-03-10 22:02*/public class ProductConstant {public enum AttrEnum {ATTR_TYPE_BASE(1, "基础属性"), ATTR_TYPE_SALE(0, "销售属性");private int code;private String message;AttrEnum(int code, String message) {this.code = code;this.message = message;}}}
2.3 AttrServiceImpl最终代码
package com.atguigu.gulimall.gulimallproduct.service.impl;import com.atguigu.common.constant.ProductConstant;import com.atguigu.gulimall.gulimallproduct.dao.AttrAttrgroupRelationDao;import com.atguigu.gulimall.gulimallproduct.dao.AttrGroupDao;import com.atguigu.gulimall.gulimallproduct.dao.CategoryDao;import com.atguigu.gulimall.gulimallproduct.entity.AttrAttrgroupRelationEntity;import com.atguigu.gulimall.gulimallproduct.entity.AttrGroupEntity;import com.atguigu.gulimall.gulimallproduct.entity.CategoryEntity;import com.atguigu.gulimall.gulimallproduct.service.CategoryService;import com.atguigu.gulimall.gulimallproduct.vo.AttrResponseVo;import com.atguigu.gulimall.gulimallproduct.vo.AttrVo;import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;import org.springframework.beans.BeanUtils;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import java.util.List;import java.util.Map;import java.util.stream.Collectors;import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;import com.baomidou.mybatisplus.core.metadata.IPage;import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;import com.atguigu.common.utils.PageUtils;import com.atguigu.common.utils.Query;import com.atguigu.gulimall.gulimallproduct.dao.AttrDao;import com.atguigu.gulimall.gulimallproduct.entity.AttrEntity;import com.atguigu.gulimall.gulimallproduct.service.AttrService;import org.springframework.transaction.annotation.Transactional;import org.springframework.util.StringUtils;@Service("attrService")public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {@AutowiredAttrAttrgroupRelationDao relationDao;@AutowiredAttrGroupDao attrGroupDao;@AutowiredCategoryDao categoryDao;@AutowiredCategoryService categoryService;@Overridepublic PageUtils queryPage(Map<String, Object> params) {IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),new QueryWrapper<AttrEntity>());return new PageUtils(page);}/*** 保存属性,同时在pms_attr_attrgroup_relation表中保存属性与属性分组的关联关系** @param attrVo 前端提交的数据*/@Transactional@Overridepublic void saveAttr(AttrVo attrVo) {// 1. 先在`pms_attr`表中保存属性的基本信息AttrEntity attrEntity = new AttrEntity();// org.springframework.beans.BeanUtils的copyProperties可以将前一个对象的属性直接拷贝给后面的对象// 前提是属性名必须一致BeanUtils.copyProperties(attrVo, attrEntity);// 保存基本数据this.save(attrEntity);// 2. 在`pms_attr_attrgroup_relation`表中,保存属性与属性分组的关联关系// 如果提交的属性类型为1,表示为基本属性,同时属性id不为空,需要将关联关系保存if (attrVo.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() && attrVo.getAttrGroupId() != null) {AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();relationEntity.setAttrId(attrEntity.getAttrId());relationEntity.setAttrGroupId(attrVo.getAttrGroupId());relationDao.insert(relationEntity);}}/*** 更新属性,同时更新属性与属性分组关联表** @param attr 前端提交的数据*/@Transactional@Overridepublic void updateAttr(AttrVo attr) {AttrEntity attrEntity = new AttrEntity();BeanUtils.copyProperties(attr, attrEntity);// 修改基本数据this.updateById(attrEntity);if (attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) { // 只有规格参数基础属性需要// 修改属性与属性关联表`pms_attr_attrgroup_relation`中的分组关联数据// UPDATE `pms_attr_attrgroup_relation` SET `attr_group_id` = ? WHERE `attr_id` = ?// todo 这里一个属性对应多个分组的情况没有考虑AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();relationEntity.setAttrGroupId(attr.getAttrGroupId());relationEntity.setAttrId(attr.getAttrId());// 查看属性和属性分组关联表中有没有attr_id对应的记录// 因为暂时是一条属性对应一个属性分组,所以可以用这种判断方法,如果是一个属性对应多个分组,则不行// todo 这里一个属性对应多个分组的情况没有考虑Long count = relationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));if (count > 0) { // 如果关联表中有则进行更新relationDao.update(relationEntity, new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));} else { // 否则就是新增——就是说属性没有跟属性分组关联,需要新建属性与属性分组关联relationDao.insert(relationEntity);}}}/*** 查询规格参数属性/销售属性列表,模糊查询,同时显示属性的属性分组名称和分类名称** @param params* @param catelogId* @param attrType* @return*/@Overridepublic PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String attrType) {/*********** 根据catelogId查询出当前目录所对应的所有属性 *************/// 这里在查询销售属性的时候还需要判断一下attr_type条件,如果传入的attrType参数是base则表示查找参数规格的基本属性// 如果是sale,则表示查找销售属性QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("attr_type", "base".equals(attrType) ? ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() : ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());// 模糊查询// 无论查全部还是查具体目录,都检查是否进行模糊查询String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.and((obj) -> {obj.eq("attr_id", key).or().like("attr_name", key);});}// 根據不同情況封裝不同條件 如果catelogId不是0,在表示查询某个具体目录的属性,设置条件查找,反之就是查询所有属性列表if (catelogId != 0) {wrapper.eq("catelog_id", catelogId);}IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params),wrapper);PageUtils pageUtils = new PageUtils(page);/*********** 查询上面查找出来的属性所对应的属性分组名和分类名 *************/// 从page中得到获取的记录——当前分类对应的所有属性List<AttrEntity> records = page.getRecords();List<AttrResponseVo> responseVoList = records.stream().map((attrEntity) -> {AttrResponseVo responseVo = new AttrResponseVo();BeanUtils.copyProperties(attrEntity, responseVo);// 在responseVo中设置分类和分组的名字// 1. 设置属性分组名// 先通过attrEntity中的attr_id在`pms_attr_attrgroup_relation`表中找到属性跟分组的记录// TODO 一个属性可能对应多个分组if ("base".equals(attrType)) { // 判断传过来的查询目标是销售属性还是规格参数属性(销售属性没有分组信息)AttrAttrgroupRelationEntity relationEntity =relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));if (relationEntity != null && relationEntity.getAttrGroupId() != null) { // 这里需要判断一下是否为空,因为属性不一定跟属性分组进行了关联// 找到记录后获取属性分组IDLong attrGroupId = relationEntity.getAttrGroupId();// 然后再根据属性分组Id到属性分组表`pms_attr_group`中查找分组名AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrGroupId);responseVo.setGroupName(attrGroupEntity.getAttrGroupName()); // 设置属性分组名}}// 2. 设置分类名// 通过responseVo/attrEntity中的catelog_id直接在`pms_category`表中找到对应的目录记录// 这个catelog_id在`pms_category`表中是cat_idCategoryEntity categoryEntity = categoryDao.selectById(responseVo.getCatelogId());if (categoryEntity != null) {responseVo.setCatelogName(categoryEntity.getName()); // 设置分类名}return responseVo;}).collect(Collectors.toList());pageUtils.setList(responseVoList); // 将处理好的结果集放到pageUtils中return pageUtils;}/*** 数据回显,并获取分类完整路径和分组信息** @param attrId* @return*/@Overridepublic AttrResponseVo getAttrInfo(Long attrId) {AttrEntity attrEntity = this.getById(attrId);AttrResponseVo responseVo = new AttrResponseVo();BeanUtils.copyProperties(attrEntity, responseVo);// 先查出当前attrId对应的属性与属性分组的关联信息// 1.设置属性分组信息if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) { // 判断传过来的查询目标是销售属性还是规格参数属性(销售属性没有分组信息)AttrAttrgroupRelationEntity attrgroupRelationEntity = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));if (attrgroupRelationEntity != null) { // 属性不一定与分组相关联了,所以查询到的记录可能为nullresponseVo.setAttrGroupId(attrgroupRelationEntity.getAttrGroupId());// 通过关联信息中的属性分组Id获取属性分组,然后获取分组名AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelationEntity.getAttrGroupId());if (attrGroupEntity != null) {responseVo.setGroupName(attrGroupEntity.getAttrGroupName());}}}// 2.设置分类信息Long catelogId = attrEntity.getCatelogId(); // 获取当前属性实体类中的分类Id// 通过分类Id查询完整路径,这个方法我们之前已经写过了,直接使用categoryService即可Long[] catelogPath = categoryService.findCatelogPath(catelogId);responseVo.setCatelogPath(catelogPath);// CategoryEntity categoryEntity1 = categoryService.getById(catelogId);CategoryEntity categoryEntity = categoryDao.selectById(catelogId);if (categoryEntity != null) {responseVo.setCatelogName(categoryEntity.getName());}return responseVo;}}
三、属性分组与属性关联
3.1 获取属性分组的关联的所有属性
接口文档
当在属性分组点击关联时,会发送http://localhost:88/api/gulimallproduct/attrgroup/1/attr/relation?t=1646930081603请求,列出当前分组关联的所有属性。
@AutowiredAttrService attrService;// /product/attrgroup/{attrgroupId}/attr/relation@GetMapping("/{attrgroupId}/attr/relation")public R attrRelation(@PathVariable("attrgroupId") Long attrgroupId) {// getRelationAttr可以获取到当前分组关联的所有属性List<AttrEntity> entities = attrService.getRelationAttr(attrgroupId);return R.ok().put("data", entities);}
老生常谈,接口声明方法,实现类实现
/*** 根据分组Id查询所有关联的基本属性(规格参数)* @param attrgroupId* @return*/@Overridepublic List<AttrEntity> getRelationAttr(Long attrgroupId) {// 先通过attrgroupId查出所有关联记录 在pms_attr_attrgroup_relation中进行查询List<AttrAttrgroupRelationEntity> relationEntities = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));// 获取关联记录中所有基本属性的attrIdList<Long> attrIds = relationEntities.stream().map((relationEntity) -> relationEntity.getAttrId()).collect(Collectors.toList());// 通过找出来的attrId获取记录if (attrIds == null || attrIds.size() == 0) {return null;}List<AttrEntity> attrEntities = this.listByIds(attrIds);return attrEntities;}
3.2 删除属性与分组的关联关系

/product/attrgroup/attr/relation/delete 删除属性分组关联的基本属性,只需要删除关联关系即可,并不需要删除基本属性。
3.2.1 视频里的写法
@AutowiredAttrService attrService;// /product/attrgroup/attr/relation/delete/*** 删除属性分组关联的基本属性,只需要删除关联关系即可,并不需要删除属性*/@PostMapping("/attr/relation/delete")public R deleteRelation(@RequestBody AttrGroupRelationVo[] vos) {attrService.deleteRelation(vos);return R.ok();}
AttrService声明&AttrServiceImpl实现
/*** 删除属性分组关联的基本属性,只需要删除关联关系即可,并不需要删除属性* @param vos*/@Overridepublic void deleteRelation(AttrGroupRelationVo[] vos) {// 批量删除关联关系// DELETE FROM `pms_attr_attrgroup_relation` WHERE (`attr_id` = 1 AND `attr_group_id` = 1) OR// (`attr_id` = 3 AND `attr_group_id` = 2)List<AttrAttrgroupRelationEntity> relationEntities = Arrays.asList(vos).stream().map((item) -> {AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();BeanUtils.copyProperties(item, relationEntity);return relationEntity;}).collect(Collectors.toList());relationDao.deleteBatchRelation(relationEntities);}
AttrAttrgroupRelationDao声明&xml中写动态SQL
<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>
3.2.2 我自己的想法
请求是发送到AttrGroupController,视频中在AttrGroupController中调用AttrService,然后再到AttrService中调用AttrAttrGroupDao。那为何不直接在AttrGroupController调用AttrAttrGroupService,让AttrAttrGroupService去调用AttrAttrGroupDao呢?
写法跟上面一样,只不过只是在AttrGroupController中调用AttrAttrGroupService,代码直接复制就行,测试ok。
测试:
3.3 新建属性分组与属性间关联

点击新建关联后,会弹出如下对话框,并发送请求,获取可以与当前属性分组关联的规格参数基本属性:http://localhost:88/api/gulimallproduct/attrgroup/1/noattr/relation?t=1646972998451&page=1&limit=10&key=
当前分组能关联的属性,一定是本分类下没有被其他分组关联的属性。
3.3.1 获取属性分组没有关联的其他属性
/product/attrgroup/{attrgroupId}/noattr/relation
这里分组与属性的关联需要遵循两个原则:
- 当前分组只能关联自己所属分类里的所有属性
- 当前分组只能关联别的分组没有引用的属性
逻辑处理流程:
/*** 查询可以与当前分组关联的所有属性*/// /product/attrgroup/{attrgroupId}/noattr/relation@GetMapping("/{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);}
/*** 获取当前分组没有关联的所有属性* @return* @param params* @param attrGroupId*/@Overridepublic PageUtils getNoRelationAttr(Map<String, Object> params, Long attrGroupId) {//1、当前分组只能关联自己所属分类里的所有属性AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrGroupId); // 通过分组Id获取当前分组信息Long catelogId = attrGroupEntity.getCatelogId(); //从分组信息中获取当前分类的Id//2、当前分组只能关联别的分组没有引用的属性//2.1 找到当前分类下的所有分组 SELECT * FROM `pms_attr_group` WHERE `catelog_id` = catelogId;List<AttrGroupEntity> group = attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));List<Long> attrGroupIds = group.stream().map((item) -> item.getAttrGroupId()).collect(Collectors.toList());//2.2 找到分组已经关联的属性List<AttrAttrgroupRelationEntity> relations = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", attrGroupIds));List<Long> attrIds = relations.stream().map((item) -> item.getAttrId()).collect(Collectors.toList());//2.3 从当前分类下的所有属性中移除这些属性——即可得到没有关联的属性// 查询的时候不仅要查本分类的,还需要排除销售属性,只查询基本属性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) { // 判断一下关联当前目录下其他分组的属性是否为空,如果不为空则需要进行not in 拼接wrapper.notIn("attr_id", attrIds);}// TODO 模糊查询是公共方法,后来可以抽取String key = (String) params.get("key");if (!StringUtils.isEmpty(key)) {wrapper.and((w) -> {w.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;}
3.3.2 新增关联关系
上面我们获取到了当前分组可以添加的关联关系,现在实现,点击新增按钮,添加关联关系。发送如下请求
http://localhost:88/api/gulimallproduct/attrgroup/attr/relation
/*** 添加属性分组与属性的关联关系 批量保存,因为一次可能选定多个属性与当前分组绑定*/// /product/attrgroup/attr/relation@PostMapping("/attr/relation")public R addRelation(@RequestBody List<AttrGroupRelationVo> vos) {relationService.saveBatch(vos);return R.ok();}
AttrAttrgroupRelationService声明AttrAttrgroupRelationServiceImpl实现
/*** 添加属性分组与属性的关联关系 批量保存,因为一次可能选定多个属性与当前分组绑定* @param vos*/@Overridepublic void saveBatch(List<AttrGroupRelationVo> vos) {List<AttrAttrgroupRelationEntity> relationEntities = vos.stream().map((vo) -> {AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();BeanUtils.copyProperties(vo, relationEntity);return relationEntity;}).collect(Collectors.toList());this.saveBatch(relationEntities);}
成功回显
