零、属性分组

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

image.png
修改gulimallproduct/attrgroup/list/{catelogId}中service层的逻辑

  1. @Override
  2. public PageUtils queryPage(Map<String, Object> params, Long catelogId) {
  3. // 一开始就判断是否进行模糊查询,修改wrapper
  4. // 前端提交的时候还会提交一个参数名,这个参数名是一个模糊查询,可以匹配任意分组字段,所以需要判断
  5. String key = (String) params.get("key");
  6. // 这里我们假设模糊查询针对name跟id
  7. // select * from pms_attr_group where catelog_id = ? and (attr_group_id = key or attr_group_name = key)
  8. QueryWrapper<AttrGroupEntity> wrapper = new QueryWrapper<AttrGroupEntity>();
  9. if (!StringUtils.isEmpty(key)) {
  10. wrapper.and((obj)->{
  11. obj.eq("attr_group_id", key).or().like("attr_group_name", key);
  12. });
  13. }
  14. // 前端规定如果传过来的catelogId为0,那么表示没有选中三级分类,则查询所有属性分组
  15. if (catelogId == 0) {
  16. IPage<AttrGroupEntity> page = this.page(
  17. new Query<AttrGroupEntity>().getPage(params),
  18. wrapper);
  19. return new PageUtils(page);
  20. }
  21. else {
  22. // 当我们需要查询特定目录的分组属性时,再设定按ID查询
  23. wrapper.eq("catelog_id", catelogId);
  24. IPage<AttrGroupEntity> page = this.page(new Query<AttrGroupEntity>().getPage(params), wrapper);
  25. return new PageUtils(page);
  26. }
  27. }

0.2 属性分组与属性建立关联

image.png

0.3 规格参数新增与VO

我们先来看规格参数,当我们点击平台属性/规格参数时,浏览器会发送请求:
http://localhost:88/api/product/attr/base/list/0?t=1646887422208&page=1&limit=10&key=获取基础属性的列表。

0.3.1 新增规格参数

image.png
image.png
发送的保存请求中,携带了属性分组的ID,但是AttrEntity实体类中并没有AttrGroupId。而AttrGroupId是存储在pms_attr_attrgroup_relation表中的,所以在进行存储的时候,同时需要更新pms_attr_attrgroup_relation表。
image.png

0.3.2 vo

当有新增字段时,我们往往会在entity实体类中新建一个字段,并标注数据库中不存在该字段,然而这种方式并不规范。
image.png
所以引入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:视图对象;接受页面传递来的数据,封装对象将业务处理完成的对象,封装成页面要用的数据。

  1. package com.atguigu.gulimall.gulimallproduct.vo;
  2. import com.baomidou.mybatisplus.annotation.TableId;
  3. import lombok.Data;
  4. @Data
  5. public class AttrVo {
  6. /**
  7. * 属性id
  8. */
  9. private Long attrId;
  10. /**
  11. * 属性名
  12. */
  13. private String attrName;
  14. /**
  15. * 是否需要检索[0-不需要,1-需要]
  16. */
  17. private Integer searchType;
  18. /**
  19. * 值类型[0-为单个值,1-可以选择多个值]
  20. */
  21. private Integer valueType;
  22. /**
  23. * 属性图标
  24. */
  25. private String icon;
  26. /**
  27. * 可选值列表[用逗号分隔]
  28. */
  29. private String valueSelect;
  30. /**
  31. * 属性类型[0-销售属性,1-基本属性,2-既是销售属性又是基本属性]
  32. */
  33. private Integer attrType;
  34. /**
  35. * 启用状态[0 - 禁用,1 - 启用]
  36. */
  37. private Long enable;
  38. /**
  39. * 所属分类
  40. */
  41. private Long catelogId;
  42. /**
  43. * 快速展示【是否展示在介绍上;0-否 1-是】,在sku中仍然可以调整
  44. */
  45. private Integer showDesc;
  46. /**
  47. * 属性分组ID
  48. */
  49. private Long attrGroupId;
  50. }

0.3.3 保存属性基本信息与关联关系

修改AttrController中的/save

  1. @RequestMapping("/save")
  2. //@RequiresPermissions("gulimallproduct:attr:save")
  3. public R save(@RequestBody AttrVo attr){ // 这里AttrEntity改成AttrVo,可以收集到AttrGroupId
  4. // attrService.save(attr);
  5. attrService.saveAttr(attr); // 这个方法会存储属性分组ID
  6. return R.ok();
  7. }

AttrService生命,AttrServiceImpl实现

  1. @Autowired
  2. AttrAttrgroupRelationDao relationDao;
  3. @Override
  4. public void saveAttr(AttrVo attr) {
  5. // 1. 先在`pms_attr`表中保存属性的基本信息
  6. AttrEntity attrEntity = new AttrEntity();
  7. // org.springframework.beans.BeanUtils的copyProperties可以将前一个对象的属性直接拷贝给后面的对象
  8. // 前提是属性名必须一致
  9. BeanUtils.copyProperties(attr, attrEntity);
  10. // 保存基本数据
  11. this.save(attrEntity);
  12. // 2. 在`pms_attr_attrgroup_relation`表中,保存属性与属性分组的关联关系
  13. AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
  14. relationEntity.setAttrId(attrEntity.getAttrId());
  15. relationEntity.setAttrGroupId(attrVo.getAttrGroupId());
  16. relationDao.insert(relationEntity);
  17. }

0.3.4 查询规格参数列表

打开规格参数的时候会发送一个查询请求:http://localhost:88/api/gulimallproduct/attr/base/list/{catelogId}?t=1646896048461&page=1&limit=10&key=
在AttrController中添加对应方法:

  1. // /base/list/{catelogId}
  2. @GetMapping("/base/list/{catelogId}")
  3. public R baseAttrList(@RequestParam Map<String, Object> params,
  4. @PathVariable("catelogId") Long catelogId) {
  5. PageUtils page = attrService.queryBaseAttrPage(params, catelogId);
  6. return R.ok().put("page", page);
  7. }

AttrService声明,AttrServiceImpl实现,同样需要考虑到模糊查询

  1. @Override
  2. public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
  3. QueryWrapper<AttrEntity> wrapper = new QueryWrapper<>();
  4. // 模糊查询
  5. // 无论查全部还是查具体目录,都检查是否进行模糊查询
  6. String key = (String) params.get("key");
  7. if (!StringUtils.isEmpty(key)) {
  8. wrapper.and((obj)->{
  9. obj.eq("attr_id", key).or().like("attr_name", key);
  10. });
  11. }
  12. // 根據不同情況封裝不同條件 如果catelogId不是0,在表示查询某个具体目录的属性
  13. if (catelogId != 0) {
  14. wrapper.eq("catelog_id", catelogId);
  15. }
  16. IPage<AttrEntity> page = this.page(
  17. new Query<AttrEntity>().getPage(params),
  18. wrapper
  19. );
  20. return new PageUtils(page);
  21. }

image.png成功查询属性列表,但是发现这里还需要显示所属分类(catelogName)和所属分组(attrGroupName)。开发文档,响应数据

0.3.5 显示所属分类和所属分组

新建OV

  1. @Data
  2. public class AttrResponseVo extends AttrVo {
  3. //所属分类名字
  4. private String catelogName;
  5. //所属分组名字
  6. private String groupName;
  7. }

修改AttrServiceImpl中的queryBaseAttrPage方法

  1. @Override
  2. public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId) {
  3. /*********** 根据catelogId查询出当前目录所对应的所有属性 *************/
  4. QueryWrapper<AttrEntity> wrapper = new QueryWrapper<>();
  5. // 模糊查询
  6. // 无论查全部还是查具体目录,都检查是否进行模糊查询
  7. String key = (String) params.get("key");
  8. if (!StringUtils.isEmpty(key)) {
  9. wrapper.and((obj) -> {
  10. obj.eq("attr_id", key).or().like("attr_name", key);
  11. });
  12. }
  13. // 根據不同情況封裝不同條件 如果catelogId不是0,在表示查询某个具体目录的属性,设置条件查找
  14. // 否则就是查询所有属性列表
  15. if (catelogId != 0) {
  16. wrapper.eq("catelog_id", catelogId);
  17. }
  18. IPage<AttrEntity> page = this.page(
  19. new Query<AttrEntity>().getPage(params),
  20. wrapper
  21. );
  22. PageUtils pageUtils = new PageUtils(page);
  23. /*********** 查询上面查找出来的属性所对应的属性分组名和分类名 *************/
  24. // 从page中得到获取的记录——当前分类对应的所有属性
  25. List<AttrEntity> records = page.getRecords();
  26. List<AttrResponseVo> responseVoList = records.stream().map((attrEntity) -> {
  27. AttrResponseVo responseVo = new AttrResponseVo();
  28. BeanUtils.copyProperties(attrEntity, responseVo);
  29. // 在responseVo中设置分类和分组的名字
  30. // 1. 设置属性分组名
  31. // 先通过attrEntity中的attr_id在`pms_attr_attrgroup_relation`表中找到属性跟分组的记录
  32. // TODO 一个属性可能对应多个分组
  33. AttrAttrgroupRelationEntity relationEntity =
  34. relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
  35. if (relationEntity != null) { // 这里需要判断一下是否为空,因为属性不一定跟属性分组进行了关联
  36. // 找到记录后获取属性分组ID
  37. Long attrGroupId = relationEntity.getAttrGroupId();
  38. // 然后再根据属性分组Id到属性分组表`pms_attr_group`中查找分组名
  39. AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrGroupId);
  40. responseVo.setGroupName(attrGroupEntity.getAttrGroupName()); // 设置属性分组名
  41. }
  42. // 2. 设置分类名
  43. // 通过responseVo/attrEntity中的catelog_id直接在`pms_category`表中找到对应的目录记录
  44. // 这个catelog_id在`pms_category`表中是cat_id
  45. CategoryEntity categoryEntity = categoryDao.selectById(responseVo.getCatelogId());
  46. if (categoryEntity != null) {
  47. responseVo.setCatelogName(categoryEntity.getName()); // 设置分类名
  48. }
  49. return responseVo;
  50. }).collect(Collectors.toList());
  51. pageUtils.setList(responseVoList); // 将处理好的结果集放到pageUtils中
  52. return pageUtils;
  53. }

这里的处理逻辑看这张图,从pms_attr出发
image.png

一、规格参数属性修改与回显

1.1 回显

image.png
编写查询属性详情接口 查询属性详情文档
给AttrResponseVo加上catelogPath属性,用来记录分类完整路径。
修改/info/{attrId}接口

  1. @RequestMapping("/info/{attrId}")
  2. //@RequiresPermissions("gulimallproduct:attr:info")
  3. public R info(@PathVariable("attrId") Long attrId){
  4. // AttrEntity attr = attrService.getById(attrId);
  5. AttrResponseVo respVo = attrService.getAttrInfo(attrId);
  6. return R.ok().put("attr", respVo);
  7. }

AtrrService中声明getAttrInfo方法,实现类实现方法

  1. /**
  2. * 数据回显,并获取分类完整路径和分组信息
  3. * @param attrId
  4. * @return
  5. */
  6. @Override
  7. public AttrResponseVo getAttrInfo(Long attrId) {
  8. AttrEntity attrEntity = this.getById(attrId);
  9. AttrResponseVo responseVo = new AttrResponseVo();
  10. BeanUtils.copyProperties(attrEntity, responseVo);
  11. // 先查出当前attrId对应的属性与属性分组的关联信息
  12. // 1.设置属性分组信息
  13. AttrAttrgroupRelationEntity attrgroupRelationEntity = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
  14. if (attrgroupRelationEntity != null) { // 属性不一定与分组相关联了,所以查询到的记录可能为null
  15. responseVo.setAttrGroupId(attrgroupRelationEntity.getAttrGroupId());
  16. // 通过关联信息中的属性分组Id获取属性分组,然后获取分组名
  17. AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelationEntity.getAttrGroupId());
  18. if (attrGroupEntity != null) {
  19. responseVo.setGroupName(attrGroupEntity.getAttrGroupName());
  20. }
  21. }
  22. // 2.设置分类信息
  23. Long catelogId = attrEntity.getCatelogId(); // 获取当前属性实体类中的分类Id
  24. // 通过分类Id查询完整路径,这个方法我们之前已经写过了,直接使用categoryService即可
  25. Long[] catelogPath = categoryService.findCatelogPath(catelogId);
  26. responseVo.setCatelogPath(catelogPath);
  27. // CategoryEntity categoryEntity1 = categoryService.getById(catelogId);
  28. CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
  29. if (categoryEntity != null) {
  30. responseVo.setCatelogName(categoryEntity.getName());
  31. }
  32. return responseVo;
  33. }

image.png成功回显

1.2 属性修改

在修改的时候发现点击确定后并没有修改成功。因为我们修改发送的是/attr/update请求,而/update方法只是更新了pms_attr表,并没有更新pms_attr_attrgroup_relation表。所以需要修改/update方法

  1. @RequestMapping("/update")
  2. //@RequiresPermissions("gulimallproduct:attr:update")
  3. public R update(@RequestBody AttrVo attr){
  4. // attrService.updateById(attr);
  5. attrService.updateAttr(attr);
  6. return R.ok();
  7. }

AtrrService中声明updateAttr方法,实现类实现方法

  1. /**
  2. * 更新属性,同时更新属性与属性分组关联表
  3. * @param attr 前端提交的数据
  4. */
  5. @Transactional
  6. @Override
  7. public void updateAttr(AttrVo attr) {
  8. AttrEntity attrEntity = new AttrEntity();
  9. BeanUtils.copyProperties(attr, attrEntity);
  10. // 修改基本数据
  11. this.updateById(attrEntity);
  12. // 修改属性与属性关联表`pms_attr_attrgroup_relation`中的数据
  13. // UPDATE `pms_attr_attrgroup_relation` SET `attr_group_id` = ? WHERE `attr_id` = ?
  14. // todo 这里一个属性对应多个分组的情况没有考虑
  15. AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
  16. relationEntity.setAttrGroupId(attr.getAttrGroupId());
  17. relationEntity.setAttrId(attr.getAttrId());
  18. // 查看属性和属性分组关联表中有没有attr_id对应的记录
  19. // 因为暂时是一条属性对应一个属性分组,所以可以用这种判断方法,如果是一个属性对应多个分组,则不行
  20. // todo 这里一个属性对应多个分组的情况没有考虑
  21. Long count = relationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
  22. if (count > 0) {
  23. relationDao.update(relationEntity, new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
  24. } else {
  25. relationDao.insert(relationEntity);
  26. }
  27. }

现在无论属性是否与属性分组关联与否,均可以进行修改。

二、销售属性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,查询参数规格属性和销售属性共用一个接口,所以直接在原来的方法上进行修改。

  1. /**
  2. * 规格参数基本属性列表 以及 销售属性共用一个方法
  3. * @param params
  4. * @param catelogId
  5. * @return
  6. */
  7. // /base/list/{catelogId}
  8. // /sale/list/{catelogId}
  9. // 添加attrType获取前端url是base还是sale以区分销售属性跟规格参数属性
  10. @GetMapping("/{attrType}/list/{catelogId}")
  11. public R baseAttrList(@RequestParam Map<String, Object> params,
  12. @PathVariable("catelogId") Long catelogId,
  13. @PathVariable("attrType") String attrType) {
  14. PageUtils page = attrService.queryBaseAttrPage(params, catelogId, attrType);
  15. return R.ok().put("page", page);
  16. }

同样,service修改声明,serviceImp修改l实现。AttrServiceImpl仅需要添加一个条件查找

  1. /**
  2. * 查询规格参数属性/销售属性列表,模糊查询,同时显示属性的属性分组名称和分类名称
  3. *
  4. * @param params
  5. * @param catelogId
  6. * @param attrType
  7. * @return
  8. */
  9. @Override
  10. public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String attrType) {
  11. /*********** 根据catelogId查询出当前目录所对应的所有属性 *************/
  12. // 这里在查询销售属性的时候还需要判断一下attr_type条件,如果传入的attrType参数是base则表示查找参数规格的基本属性
  13. // 如果是sale,则表示查找销售属性
  14. QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("attr_type", "base".equals(attrType) ? 1 : 0);
  15. // 模糊查询
  16. // 无论查全部还是查具体目录,都检查是否进行模糊查询
  17. String key = (String) params.get("key");
  18. if (!StringUtils.isEmpty(key)) {
  19. wrapper.and((obj) -> {
  20. obj.eq("attr_id", key).or().like("attr_name", key);
  21. });
  22. }
  23. // 根據不同情況封裝不同條件 如果catelogId不是0,在表示查询某个具体目录的属性,设置条件查找,反之就是查询所有属性列表
  24. if (catelogId != 0) {
  25. wrapper.eq("catelog_id", catelogId);
  26. }
  27. IPage<AttrEntity> page = this.page(
  28. new Query<AttrEntity>().getPage(params),
  29. wrapper
  30. );
  31. PageUtils pageUtils = new PageUtils(page);
  32. /*********** 查询上面查找出来的属性所对应的属性分组名和分类名 *************/
  33. // 从page中得到获取的记录——当前分类对应的所有属性
  34. List<AttrEntity> records = page.getRecords();
  35. List<AttrResponseVo> responseVoList = records.stream().map((attrEntity) -> {
  36. AttrResponseVo responseVo = new AttrResponseVo();
  37. BeanUtils.copyProperties(attrEntity, responseVo);
  38. // 在responseVo中设置分类和分组的名字
  39. // 1. 设置属性分组名
  40. // 先通过attrEntity中的attr_id在`pms_attr_attrgroup_relation`表中找到属性跟分组的记录
  41. // TODO 一个属性可能对应多个分组
  42. AttrAttrgroupRelationEntity relationEntity =
  43. relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
  44. if (relationEntity != null) { // 这里需要判断一下是否为空,因为属性不一定跟属性分组进行了关联
  45. // 找到记录后获取属性分组ID
  46. Long attrGroupId = relationEntity.getAttrGroupId();
  47. // 然后再根据属性分组Id到属性分组表`pms_attr_group`中查找分组名
  48. AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrGroupId);
  49. responseVo.setGroupName(attrGroupEntity.getAttrGroupName()); // 设置属性分组名
  50. }
  51. // 2. 设置分类名
  52. // 通过responseVo/attrEntity中的catelog_id直接在`pms_category`表中找到对应的目录记录
  53. // 这个catelog_id在`pms_category`表中是cat_id
  54. CategoryEntity categoryEntity = categoryDao.selectById(responseVo.getCatelogId());
  55. if (categoryEntity != null) {
  56. responseVo.setCatelogName(categoryEntity.getName()); // 设置分类名
  57. }
  58. return responseVo;
  59. }).collect(Collectors.toList());
  60. pageUtils.setList(responseVoList); // 将处理好的结果集放到pageUtils中
  61. return pageUtils;
  62. }

image.png

2.2 新增分类销售属性

点击新增后,数据库中有数据但是没有在销售属性中展示。检查后端输出日志,发现报错空指针异常:
image.png
原因是销售属性并不存在属性分组。所以在设置分组信息时,需要进行一个判断。(最终代码直接放后面了,这里要改的太多了)
添加销售属性时pms_attr_attrgroup_relation,会出现一条没有attr_group_id的记录,这是因为共用了规格参数属性的接口,因此需要加一个判断。
image.png
由于经常需要判断attrType,直接在common中定义一个常量类

  1. package com.atguigu.common.constant;
  2. /**
  3. * 常量类
  4. * @author mrlinxi
  5. * @create 2022-03-10 22:02
  6. */
  7. public class ProductConstant {
  8. public enum AttrEnum {
  9. ATTR_TYPE_BASE(1, "基础属性"), ATTR_TYPE_SALE(0, "销售属性");
  10. private int code;
  11. private String message;
  12. AttrEnum(int code, String message) {
  13. this.code = code;
  14. this.message = message;
  15. }
  16. }
  17. }

2.3 AttrServiceImpl最终代码

  1. package com.atguigu.gulimall.gulimallproduct.service.impl;
  2. import com.atguigu.common.constant.ProductConstant;
  3. import com.atguigu.gulimall.gulimallproduct.dao.AttrAttrgroupRelationDao;
  4. import com.atguigu.gulimall.gulimallproduct.dao.AttrGroupDao;
  5. import com.atguigu.gulimall.gulimallproduct.dao.CategoryDao;
  6. import com.atguigu.gulimall.gulimallproduct.entity.AttrAttrgroupRelationEntity;
  7. import com.atguigu.gulimall.gulimallproduct.entity.AttrGroupEntity;
  8. import com.atguigu.gulimall.gulimallproduct.entity.CategoryEntity;
  9. import com.atguigu.gulimall.gulimallproduct.service.CategoryService;
  10. import com.atguigu.gulimall.gulimallproduct.vo.AttrResponseVo;
  11. import com.atguigu.gulimall.gulimallproduct.vo.AttrVo;
  12. import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
  13. import org.springframework.beans.BeanUtils;
  14. import org.springframework.beans.factory.annotation.Autowired;
  15. import org.springframework.stereotype.Service;
  16. import java.util.List;
  17. import java.util.Map;
  18. import java.util.stream.Collectors;
  19. import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
  20. import com.baomidou.mybatisplus.core.metadata.IPage;
  21. import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
  22. import com.atguigu.common.utils.PageUtils;
  23. import com.atguigu.common.utils.Query;
  24. import com.atguigu.gulimall.gulimallproduct.dao.AttrDao;
  25. import com.atguigu.gulimall.gulimallproduct.entity.AttrEntity;
  26. import com.atguigu.gulimall.gulimallproduct.service.AttrService;
  27. import org.springframework.transaction.annotation.Transactional;
  28. import org.springframework.util.StringUtils;
  29. @Service("attrService")
  30. public class AttrServiceImpl extends ServiceImpl<AttrDao, AttrEntity> implements AttrService {
  31. @Autowired
  32. AttrAttrgroupRelationDao relationDao;
  33. @Autowired
  34. AttrGroupDao attrGroupDao;
  35. @Autowired
  36. CategoryDao categoryDao;
  37. @Autowired
  38. CategoryService categoryService;
  39. @Override
  40. public PageUtils queryPage(Map<String, Object> params) {
  41. IPage<AttrEntity> page = this.page(
  42. new Query<AttrEntity>().getPage(params),
  43. new QueryWrapper<AttrEntity>()
  44. );
  45. return new PageUtils(page);
  46. }
  47. /**
  48. * 保存属性,同时在pms_attr_attrgroup_relation表中保存属性与属性分组的关联关系
  49. *
  50. * @param attrVo 前端提交的数据
  51. */
  52. @Transactional
  53. @Override
  54. public void saveAttr(AttrVo attrVo) {
  55. // 1. 先在`pms_attr`表中保存属性的基本信息
  56. AttrEntity attrEntity = new AttrEntity();
  57. // org.springframework.beans.BeanUtils的copyProperties可以将前一个对象的属性直接拷贝给后面的对象
  58. // 前提是属性名必须一致
  59. BeanUtils.copyProperties(attrVo, attrEntity);
  60. // 保存基本数据
  61. this.save(attrEntity);
  62. // 2. 在`pms_attr_attrgroup_relation`表中,保存属性与属性分组的关联关系
  63. // 如果提交的属性类型为1,表示为基本属性,同时属性id不为空,需要将关联关系保存
  64. if (attrVo.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() && attrVo.getAttrGroupId() != null) {
  65. AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
  66. relationEntity.setAttrId(attrEntity.getAttrId());
  67. relationEntity.setAttrGroupId(attrVo.getAttrGroupId());
  68. relationDao.insert(relationEntity);
  69. }
  70. }
  71. /**
  72. * 更新属性,同时更新属性与属性分组关联表
  73. *
  74. * @param attr 前端提交的数据
  75. */
  76. @Transactional
  77. @Override
  78. public void updateAttr(AttrVo attr) {
  79. AttrEntity attrEntity = new AttrEntity();
  80. BeanUtils.copyProperties(attr, attrEntity);
  81. // 修改基本数据
  82. this.updateById(attrEntity);
  83. if (attr.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) { // 只有规格参数基础属性需要
  84. // 修改属性与属性关联表`pms_attr_attrgroup_relation`中的分组关联数据
  85. // UPDATE `pms_attr_attrgroup_relation` SET `attr_group_id` = ? WHERE `attr_id` = ?
  86. // todo 这里一个属性对应多个分组的情况没有考虑
  87. AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
  88. relationEntity.setAttrGroupId(attr.getAttrGroupId());
  89. relationEntity.setAttrId(attr.getAttrId());
  90. // 查看属性和属性分组关联表中有没有attr_id对应的记录
  91. // 因为暂时是一条属性对应一个属性分组,所以可以用这种判断方法,如果是一个属性对应多个分组,则不行
  92. // todo 这里一个属性对应多个分组的情况没有考虑
  93. Long count = relationDao.selectCount(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
  94. if (count > 0) { // 如果关联表中有则进行更新
  95. relationDao.update(relationEntity, new UpdateWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attr.getAttrId()));
  96. } else { // 否则就是新增——就是说属性没有跟属性分组关联,需要新建属性与属性分组关联
  97. relationDao.insert(relationEntity);
  98. }
  99. }
  100. }
  101. /**
  102. * 查询规格参数属性/销售属性列表,模糊查询,同时显示属性的属性分组名称和分类名称
  103. *
  104. * @param params
  105. * @param catelogId
  106. * @param attrType
  107. * @return
  108. */
  109. @Override
  110. public PageUtils queryBaseAttrPage(Map<String, Object> params, Long catelogId, String attrType) {
  111. /*********** 根据catelogId查询出当前目录所对应的所有属性 *************/
  112. // 这里在查询销售属性的时候还需要判断一下attr_type条件,如果传入的attrType参数是base则表示查找参数规格的基本属性
  113. // 如果是sale,则表示查找销售属性
  114. QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>()
  115. .eq("attr_type", "base".equals(attrType) ? ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode() : ProductConstant.AttrEnum.ATTR_TYPE_SALE.getCode());
  116. // 模糊查询
  117. // 无论查全部还是查具体目录,都检查是否进行模糊查询
  118. String key = (String) params.get("key");
  119. if (!StringUtils.isEmpty(key)) {
  120. wrapper.and((obj) -> {
  121. obj.eq("attr_id", key).or().like("attr_name", key);
  122. });
  123. }
  124. // 根據不同情況封裝不同條件 如果catelogId不是0,在表示查询某个具体目录的属性,设置条件查找,反之就是查询所有属性列表
  125. if (catelogId != 0) {
  126. wrapper.eq("catelog_id", catelogId);
  127. }
  128. IPage<AttrEntity> page = this.page(
  129. new Query<AttrEntity>().getPage(params),
  130. wrapper
  131. );
  132. PageUtils pageUtils = new PageUtils(page);
  133. /*********** 查询上面查找出来的属性所对应的属性分组名和分类名 *************/
  134. // 从page中得到获取的记录——当前分类对应的所有属性
  135. List<AttrEntity> records = page.getRecords();
  136. List<AttrResponseVo> responseVoList = records.stream().map((attrEntity) -> {
  137. AttrResponseVo responseVo = new AttrResponseVo();
  138. BeanUtils.copyProperties(attrEntity, responseVo);
  139. // 在responseVo中设置分类和分组的名字
  140. // 1. 设置属性分组名
  141. // 先通过attrEntity中的attr_id在`pms_attr_attrgroup_relation`表中找到属性跟分组的记录
  142. // TODO 一个属性可能对应多个分组
  143. if ("base".equals(attrType)) { // 判断传过来的查询目标是销售属性还是规格参数属性(销售属性没有分组信息)
  144. AttrAttrgroupRelationEntity relationEntity =
  145. relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrEntity.getAttrId()));
  146. if (relationEntity != null && relationEntity.getAttrGroupId() != null) { // 这里需要判断一下是否为空,因为属性不一定跟属性分组进行了关联
  147. // 找到记录后获取属性分组ID
  148. Long attrGroupId = relationEntity.getAttrGroupId();
  149. // 然后再根据属性分组Id到属性分组表`pms_attr_group`中查找分组名
  150. AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrGroupId);
  151. responseVo.setGroupName(attrGroupEntity.getAttrGroupName()); // 设置属性分组名
  152. }
  153. }
  154. // 2. 设置分类名
  155. // 通过responseVo/attrEntity中的catelog_id直接在`pms_category`表中找到对应的目录记录
  156. // 这个catelog_id在`pms_category`表中是cat_id
  157. CategoryEntity categoryEntity = categoryDao.selectById(responseVo.getCatelogId());
  158. if (categoryEntity != null) {
  159. responseVo.setCatelogName(categoryEntity.getName()); // 设置分类名
  160. }
  161. return responseVo;
  162. }).collect(Collectors.toList());
  163. pageUtils.setList(responseVoList); // 将处理好的结果集放到pageUtils中
  164. return pageUtils;
  165. }
  166. /**
  167. * 数据回显,并获取分类完整路径和分组信息
  168. *
  169. * @param attrId
  170. * @return
  171. */
  172. @Override
  173. public AttrResponseVo getAttrInfo(Long attrId) {
  174. AttrEntity attrEntity = this.getById(attrId);
  175. AttrResponseVo responseVo = new AttrResponseVo();
  176. BeanUtils.copyProperties(attrEntity, responseVo);
  177. // 先查出当前attrId对应的属性与属性分组的关联信息
  178. // 1.设置属性分组信息
  179. if (attrEntity.getAttrType() == ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode()) { // 判断传过来的查询目标是销售属性还是规格参数属性(销售属性没有分组信息)
  180. AttrAttrgroupRelationEntity attrgroupRelationEntity = relationDao.selectOne(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_id", attrId));
  181. if (attrgroupRelationEntity != null) { // 属性不一定与分组相关联了,所以查询到的记录可能为null
  182. responseVo.setAttrGroupId(attrgroupRelationEntity.getAttrGroupId());
  183. // 通过关联信息中的属性分组Id获取属性分组,然后获取分组名
  184. AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrgroupRelationEntity.getAttrGroupId());
  185. if (attrGroupEntity != null) {
  186. responseVo.setGroupName(attrGroupEntity.getAttrGroupName());
  187. }
  188. }
  189. }
  190. // 2.设置分类信息
  191. Long catelogId = attrEntity.getCatelogId(); // 获取当前属性实体类中的分类Id
  192. // 通过分类Id查询完整路径,这个方法我们之前已经写过了,直接使用categoryService即可
  193. Long[] catelogPath = categoryService.findCatelogPath(catelogId);
  194. responseVo.setCatelogPath(catelogPath);
  195. // CategoryEntity categoryEntity1 = categoryService.getById(catelogId);
  196. CategoryEntity categoryEntity = categoryDao.selectById(catelogId);
  197. if (categoryEntity != null) {
  198. responseVo.setCatelogName(categoryEntity.getName());
  199. }
  200. return responseVo;
  201. }
  202. }

三、属性分组与属性关联

3.1 获取属性分组的关联的所有属性

接口文档
当在属性分组点击关联时,会发送http://localhost:88/api/gulimallproduct/attrgroup/1/attr/relation?t=1646930081603请求,列出当前分组关联的所有属性。

  1. @Autowired
  2. AttrService attrService;
  3. // /product/attrgroup/{attrgroupId}/attr/relation
  4. @GetMapping("/{attrgroupId}/attr/relation")
  5. public R attrRelation(@PathVariable("attrgroupId") Long attrgroupId) {
  6. // getRelationAttr可以获取到当前分组关联的所有属性
  7. List<AttrEntity> entities = attrService.getRelationAttr(attrgroupId);
  8. return R.ok().put("data", entities);
  9. }

老生常谈,接口声明方法,实现类实现

  1. /**
  2. * 根据分组Id查询所有关联的基本属性(规格参数)
  3. * @param attrgroupId
  4. * @return
  5. */
  6. @Override
  7. public List<AttrEntity> getRelationAttr(Long attrgroupId) {
  8. // 先通过attrgroupId查出所有关联记录 在pms_attr_attrgroup_relation中进行查询
  9. List<AttrAttrgroupRelationEntity> relationEntities = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));
  10. // 获取关联记录中所有基本属性的attrId
  11. List<Long> attrIds = relationEntities.stream().map((relationEntity) -> relationEntity.getAttrId()).collect(Collectors.toList());
  12. // 通过找出来的attrId获取记录
  13. if (attrIds == null || attrIds.size() == 0) {
  14. return null;
  15. }
  16. List<AttrEntity> attrEntities = this.listByIds(attrIds);
  17. return attrEntities;
  18. }

3.2 删除属性与分组的关联关系

image.png
/product/attrgroup/attr/relation/delete 删除属性分组关联的基本属性,只需要删除关联关系即可,并不需要删除基本属性。

3.2.1 视频里的写法

  1. @Autowired
  2. AttrService attrService;
  3. // /product/attrgroup/attr/relation/delete
  4. /**
  5. * 删除属性分组关联的基本属性,只需要删除关联关系即可,并不需要删除属性
  6. */
  7. @PostMapping("/attr/relation/delete")
  8. public R deleteRelation(@RequestBody AttrGroupRelationVo[] vos) {
  9. attrService.deleteRelation(vos);
  10. return R.ok();
  11. }

AttrService声明&AttrServiceImpl实现

  1. /**
  2. * 删除属性分组关联的基本属性,只需要删除关联关系即可,并不需要删除属性
  3. * @param vos
  4. */
  5. @Override
  6. public void deleteRelation(AttrGroupRelationVo[] vos) {
  7. // 批量删除关联关系
  8. // DELETE FROM `pms_attr_attrgroup_relation` WHERE (`attr_id` = 1 AND `attr_group_id` = 1) OR
  9. // (`attr_id` = 3 AND `attr_group_id` = 2)
  10. List<AttrAttrgroupRelationEntity> relationEntities = Arrays.asList(vos).stream().map((item) -> {
  11. AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
  12. BeanUtils.copyProperties(item, relationEntity);
  13. return relationEntity;
  14. }).collect(Collectors.toList());
  15. relationDao.deleteBatchRelation(relationEntities);
  16. }

AttrAttrgroupRelationDao声明&xml中写动态SQL

  1. <delete id="deleteBatchRelation">
  2. DELETE FROM `pms_attr_attrgroup_relation` WHERE
  3. <foreach collection="entities" item="item" separator=" OR ">
  4. (`attr_id` = #{item.attrId} AND `attr_group_id` = #{item.attrGroupId})
  5. </foreach>
  6. </delete>

3.2.2 我自己的想法

请求是发送到AttrGroupController,视频中在AttrGroupController中调用AttrService,然后再到AttrService中调用AttrAttrGroupDao。那为何不直接在AttrGroupController调用AttrAttrGroupService,让AttrAttrGroupService去调用AttrAttrGroupDao呢?
写法跟上面一样,只不过只是在AttrGroupController中调用AttrAttrGroupService,代码直接复制就行,测试ok。
测试:
image.png

3.3 新建属性分组与属性间关联

image.png
点击新建关联后,会弹出如下对话框,并发送请求,获取可以与当前属性分组关联的规格参数基本属性:http://localhost:88/api/gulimallproduct/attrgroup/1/noattr/relation?t=1646972998451&page=1&limit=10&key=
image.png
当前分组能关联的属性,一定是本分类下没有被其他分组关联的属性。

3.3.1 获取属性分组没有关联的其他属性

/product/attrgroup/{attrgroupId}/noattr/relation
这里分组与属性的关联需要遵循两个原则:

  1. 当前分组只能关联自己所属分类里的所有属性
  2. 当前分组只能关联别的分组没有引用的属性

逻辑处理流程:
image.png

  1. /**
  2. * 查询可以与当前分组关联的所有属性
  3. */
  4. // /product/attrgroup/{attrgroupId}/noattr/relation
  5. @GetMapping("/{attrgroupId}/noattr/relation")
  6. public R attrNoRelation(@RequestParam Map<String, Object> params,
  7. @PathVariable("attrgroupId") Long attrGroupId) {
  8. // 这是一个分页方法,能获取所有能与当前分组关联且未与其他分组关联的所有属性
  9. PageUtils page = attrService.getNoRelationAttr(params, attrGroupId);
  10. return R.ok().put("page", page);
  11. }
  1. /**
  2. * 获取当前分组没有关联的所有属性
  3. * @return
  4. * @param params
  5. * @param attrGroupId
  6. */
  7. @Override
  8. public PageUtils getNoRelationAttr(Map<String, Object> params, Long attrGroupId) {
  9. //1、当前分组只能关联自己所属分类里的所有属性
  10. AttrGroupEntity attrGroupEntity = attrGroupDao.selectById(attrGroupId); // 通过分组Id获取当前分组信息
  11. Long catelogId = attrGroupEntity.getCatelogId(); //从分组信息中获取当前分类的Id
  12. //2、当前分组只能关联别的分组没有引用的属性
  13. //2.1 找到当前分类下的所有分组 SELECT * FROM `pms_attr_group` WHERE `catelog_id` = catelogId;
  14. List<AttrGroupEntity> group = attrGroupDao.selectList(new QueryWrapper<AttrGroupEntity>().eq("catelog_id", catelogId));
  15. List<Long> attrGroupIds = group.stream().map((item) -> item.getAttrGroupId()).collect(Collectors.toList());
  16. //2.2 找到分组已经关联的属性
  17. List<AttrAttrgroupRelationEntity> relations = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().in("attr_group_id", attrGroupIds));
  18. List<Long> attrIds = relations.stream().map((item) -> item.getAttrId()).collect(Collectors.toList());
  19. //2.3 从当前分类下的所有属性中移除这些属性——即可得到没有关联的属性
  20. // 查询的时候不仅要查本分类的,还需要排除销售属性,只查询基本属性
  21. QueryWrapper<AttrEntity> wrapper = new QueryWrapper<AttrEntity>().eq("catelog_Id", catelogId).eq("attr_type", ProductConstant.AttrEnum.ATTR_TYPE_BASE.getCode());
  22. if (attrIds != null && attrIds.size() > 0) { // 判断一下关联当前目录下其他分组的属性是否为空,如果不为空则需要进行not in 拼接
  23. wrapper.notIn("attr_id", attrIds);
  24. }
  25. // TODO 模糊查询是公共方法,后来可以抽取
  26. String key = (String) params.get("key");
  27. if (!StringUtils.isEmpty(key)) {
  28. wrapper.and((w) -> {
  29. w.eq("attr_id", key).or().like("attr_name", key);
  30. });
  31. }
  32. IPage<AttrEntity> page = this.page(new Query<AttrEntity>().getPage(params), wrapper);
  33. PageUtils pageUtils = new PageUtils(page);
  34. return pageUtils;
  35. }

3.3.2 新增关联关系

上面我们获取到了当前分组可以添加的关联关系,现在实现,点击新增按钮,添加关联关系。发送如下请求
http://localhost:88/api/gulimallproduct/attrgroup/attr/relation

  1. /**
  2. * 添加属性分组与属性的关联关系 批量保存,因为一次可能选定多个属性与当前分组绑定
  3. */
  4. // /product/attrgroup/attr/relation
  5. @PostMapping("/attr/relation")
  6. public R addRelation(@RequestBody List<AttrGroupRelationVo> vos) {
  7. relationService.saveBatch(vos);
  8. return R.ok();
  9. }

AttrAttrgroupRelationService声明AttrAttrgroupRelationServiceImpl实现

  1. /**
  2. * 添加属性分组与属性的关联关系 批量保存,因为一次可能选定多个属性与当前分组绑定
  3. * @param vos
  4. */
  5. @Override
  6. public void saveBatch(List<AttrGroupRelationVo> vos) {
  7. List<AttrAttrgroupRelationEntity> relationEntities = vos.stream().map((vo) -> {
  8. AttrAttrgroupRelationEntity relationEntity = new AttrAttrgroupRelationEntity();
  9. BeanUtils.copyProperties(vo, relationEntity);
  10. return relationEntity;
  11. }).collect(Collectors.toList());
  12. this.saveBatch(relationEntities);
  13. }