Search K
Appearance
Appearance
接下来我们需要了解商品相关表的设计思想,可以说表的设计是一个业务的核心驱动,电商表也是十分的复杂,下面要学习点商表的设计。
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
/**
* 列表
*/
@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
@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
匹配,最开始我使用的第一种写法是错误的
错误写法:
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
的匹配作为一个整体条件
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
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
/**
* 信息
*/
@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
Long[] findCatelogPath(Long catelogId);
com.atguigu.gulimall.product.service.impl.CategoryServiceImpl
/**
* 根据分类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
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
/**
* 品牌分类关联
*
* @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
/**
* 品牌分类关联
*
* @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
@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
/**
* 保存
*/
@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_name
和catelog_name
都是冗余字段。如果有一天我们更新了品牌或者分类名称,那么这里关联表中的数据就是有问题了,所以我们在更新品牌和分类名称时也要同步更新关联表中存在并且对应的数据。
com.atguigu.gulimall.product.controller.BrandController
/**
* 修改
*/
@RequestMapping("/update")
public R update(@Validated({UpdateGroup.class}) @RequestBody BrandEntity brand){
brandService.updateDetails(brand);
return R.ok();
}
com.atguigu.gulimall.product.service.impl.BrandServiceImpl
@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
@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
/**
* 修改
*/
@RequestMapping("/update")
public R update(@RequestBody CategoryEntity category){
categoryService.updateDetails(category);
return R.ok();
}
com.atguigu.gulimall.product.service.impl.CategoryServiceImpl
@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
@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
@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
/**
* 保存
*/
@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
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
void saveAttr(AttrVo attr);
com.atguigu.gulimall.product.service.impl.AttrServiceImpl
@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
/**
* 列表
*/
@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
PageUtils getBaseList(Map<String, Object> params,Long catelogId);
com.atguigu.gulimall.product.service.impl.AttrServiceImpl
@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
/**
* 根据分类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
/**
* 信息
*/
@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
AttrResVo getInfo(Long attrId);
com.atguigu.gulimall.product.service.impl.AttrServiceImpl
@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
/**
* 删除
*/
@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
void deleteWithGroup(Long attrId);
com.atguigu.gulimall.product.service.impl.AttrServiceImpl
@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
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
/**
* 列表
*/
@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
PageUtils getBaseList(Map<String, Object> params,Long catelogId,String type);
com.atguigu.gulimall.product.service.impl.AttrServiceImpl
@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
@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
@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
@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
/**
* 根据分组查询关联的属性
*/
@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
/**
* 根据分组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
/**
* 根据属性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
/**
* 传入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
/**
* 添加属性与分组关联关系
*/
@PostMapping("/attr/relation")
public R relation(@RequestBody List<AttrAttrgroupRelationEntity> attrAttrgroupRelationEntity){
attrAttrgroupRelationService.relation(attrAttrgroupRelationEntity);
return R.ok();
}
com.atguigu.gulimall.product.service.impl.AttrAttrgroupRelationServiceImpl
public void relation(List<AttrAttrgroupRelationEntity> attrAttrgroupRelationEntity) {
this.saveBatch(attrAttrgroupRelationEntity);
}
发布商品页面会调用获取会员等级列表的接口,所以需要先添加该接口,目前改接口404,是因为网关服务还未添加对应的配置
gulimall-gateway/src/main/resources/application.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
/**
* 根据分类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
@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;
}