零、属性分组
0.1 属性分组查询全部增加模糊查询
修改gulimallproduct/attrgroup/list/{catelogId}中service层的逻辑
@Override
public 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;
@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;
}
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); // 这个方法会存储属性分组ID
return R.ok();
}
AttrService生命,AttrServiceImpl实现
@Autowired
AttrAttrgroupRelationDao relationDao;
@Override
public 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实现,同样需要考虑到模糊查询
@Override
public 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
@Data
public class AttrResponseVo extends AttrVo {
//所属分类名字
private String catelogName;
//所属分组名字
private String groupName;
}
修改AttrServiceImpl中的queryBaseAttrPage方法
@Override
public 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) { // 这里需要判断一下是否为空,因为属性不一定跟属性分组进行了关联
// 找到记录后获取属性分组ID
Long 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_id
CategoryEntity 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
*/
@Override
public 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) { // 属性不一定与分组相关联了,所以查询到的记录可能为null
responseVo.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
@Override
public 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
*/
@Override
public 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) { // 这里需要判断一下是否为空,因为属性不一定跟属性分组进行了关联
// 找到记录后获取属性分组ID
Long 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_id
CategoryEntity 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 {
@Autowired
AttrAttrgroupRelationDao relationDao;
@Autowired
AttrGroupDao attrGroupDao;
@Autowired
CategoryDao categoryDao;
@Autowired
CategoryService categoryService;
@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);
}
/**
* 保存属性,同时在pms_attr_attrgroup_relation表中保存属性与属性分组的关联关系
*
* @param attrVo 前端提交的数据
*/
@Transactional
@Override
public 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
@Override
public 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
*/
@Override
public 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) { // 这里需要判断一下是否为空,因为属性不一定跟属性分组进行了关联
// 找到记录后获取属性分组ID
Long 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_id
CategoryEntity 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
*/
@Override
public 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) { // 属性不一定与分组相关联了,所以查询到的记录可能为null
responseVo.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请求,列出当前分组关联的所有属性。
@Autowired
AttrService 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
*/
@Override
public List<AttrEntity> getRelationAttr(Long attrgroupId) {
// 先通过attrgroupId查出所有关联记录 在pms_attr_attrgroup_relation中进行查询
List<AttrAttrgroupRelationEntity> relationEntities = relationDao.selectList(new QueryWrapper<AttrAttrgroupRelationEntity>().eq("attr_group_id", attrgroupId));
// 获取关联记录中所有基本属性的attrId
List<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 视频里的写法
@Autowired
AttrService 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
*/
@Override
public 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
*/
@Override
public 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
*/
@Override
public 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);
}