分析一下spu与sku的数据结构
再来说一下什么是spu
standard product unit 标准产品单元 :SPU级别的规格参数通常是与整个产品类型或产品系列相关的通用参数。比如华为手机下面的p系列、荣耀系列,都可以标识为spu级别规格参数
sku
stock keeping unit 库存保管单位:SKU级别的规格参数是具体到每个独立的产品变种(SKU)的特有参数。它们描述了每个SKU的唯一特性,如颜色、型号、配置等。
比如对于同一个spu,下面不同的颜色名称,内存大小,内核数,都构成了不同的sku参数
那么spu表是整个产品的相关通用信息,具体我们要设计什么样的字段
我们大致想一下,id,title有个标题,sub_title子标题可能有,可能没有,然后我们还要考虑与分类的关系,我们之前是通过手机这个分类,找到它相关的参数组,然后通过参数组,又去找它具体的子参数,具体的子参数找到了,然后我们就要去找它具体的值,这个值在什么地方存在?我们这里只是简单的设想,这个值是分为了通用参数值,也就是这个参数值大家一起共用,肯定是存放在tb_spu这张表里面,那如果是特殊的值,那就是在sku里面获取。
上面的大致思想就可以用下面这张图来表示
回过头来,继续分析字段,那里面还需要有cid,也就是分类的id,只有拿到分类id,才能找到参数组
然后这个表和品牌表brand表的关系,一个品牌会有多个spu ,比如华为,华为下面有x1系列,x2系列......系列的手机,他每一个手机系列都是一个spu,所以应该有一个brand_id,另外可能就是一些杂七杂八的东西,比如saleable是否上架,valid是否还有效,创建时间和最后修改的时间
那么我们下面就把这个tb_spu表给创建出来
但是再去仔细分析一下这张表,也就是说,这张表有没有什么缺漏
,似乎没有商品的描述,也没有我们通用规格参数的部分,还有一些共用的,比如包装清单啊,比如售后服务啊之类的
这些的数据比较大,为了不影响主表的查询效率,我们就把这些数据拆分了出来
这张表的名字就叫tb_spu_detail,这个表其实就是tb_spu的里面的内容,那我们分析一下字段,上面就是简单的分析了一把
spu_id这个必有嘛,因为它本身就是spu里面的东西
description text 存放的是商品的描述信息
text这个字段我来解释一下
packing_list varchar(3000) 这些字段设计的都是比较大的,这个是包装清单
after_service varchar(3000) 售后服务
下面重点说一下下面两个字段
generic_spec里面保存的是什么?保存的其实就是通用规格参数数据,这些数据就是每一个spu都会固定的东西,就是说,你不管点哪一个spu,这个通用规格参数都一样
我们先来看一下这个generic_spec这样一个字段,它保存的是什么样的信息
说一下,通用规格参数这个参数名是放在tb_spec_param这张表里面的,所以这里设定的是通用值 ,那么如果说,仅仅存放值,没有键的话,他知道是哪一个参数的值吗?很明显不知道,所以这里左边给了tb_spec_param这个表里面的键,右边是值
下面我们再去看一下special_spec这个字段是怎么样的,这个是保存的sku的特有属性,你在spu里面不具备这样的属性。比如内存,颜色,这就是sku,比如下面这一段参数
这里面为什么上面每一个参数,它对应的值都是数组类型的呢?
因为很简单哪怕就是说,同一个spu下面每一个sku,其值都不一样,所以值会有很多,形成数组
上面说了,说special_spec这个字段,存放的是sku特有的属性,所以这个special_spec里面存放的是规格参数对应的内容数组选项,前者为什么是规格参数,因为具体的参数还是放到tb_spec_param里面,你要给它赋值,就对应tb_spec_param里面的id,然后值呢,是一个数组类型,可供选择。下面我们看一下格式
下面我们要把从spu拆分出来的细节表tb_spu_detail给做出来
下面我们就要去分析sku表了
这张表是特有属性信息,那简单来分析一下它的字段
id,spu_id(它是哪一个商品下面的sku啊),title'存放商品的标题',images存放图片,这里可能要存放图片的地址,还有price价格,enable这个商品是否有效啊这样一个判定,创建时间,最后修改时间
还是那句话,重点关注下面两个字段
这个indexes一看名字就是一个索引的表示?你说它怎么来表示首先我们刚刚分析了一下spu表,我们是在spu表里面插入了特有属性的,这个特有属性它是键值对
我们查询spu的时候,根据不同的sku组合,我们需要取出这些特殊的键值对 ,那么这里的设计就是,我们选择上面各自分组当中的哪一个值,也就是下面这些组合当中,我们从中怎么去抽取
继续往下
先来贴一张他实际存放的值吧
因此这里存放的就是
下面再来讲own_spec字段
下面我们要向上面的表插入信息,先向tb_spu表插入数据
看一下数据库
在向tb_spu_detail这张表插入商品信息
看一下这张表的大致内容
下面向tb_sku里面插入一些信息
下面我们去实现商品查询的部分
先去分析一下前端页面,首先点击下面这个位置
就直接会走一个查询
很明显上面就是spu表的一个查询
这个商品查询需要实现的效果是如下
下面分析页面
找到Goods.vue组件
上面看路径我们就知道,很明显这是对spu表进行的一个查询
在写具体的业务逻辑之前,先来看一下其他方面的基础知识
1.PageHelper怎么使用
首先我们必须知道它是Mybiitas中实现分页的插件,它的原理是通过拦截Mybatis执行sql语句
我们只需要在服务端,比如service这个地方,就是写业务代码的地方,,在真正的sql实现之前去开启
注意它这时候给我们只是拼接了sql语句,没有做别的
类似于下面的拼接
他会在前端每一次比如点击当前页面显示多少数据,比如下一页,他又会传进来进行执行上面的语句
在说回来,那我们要给前端一个什么呢?我们就必须传过去关于分页的信息数据,前端才可以获取使用,同时还有实际对象数据
所以必须传过去PageResult对象
2.采用Stream流里面的map方法,把一种流变成另外一种流
下面直接拿出一个实际案例来讲一下
3.下面说几个开发过程中的常用工具
1.BeanUtils工具类
这个是Spring给我们提供的操作对象的工具
上面就是把一个对象的数据复制给另外一个对象
2.StringUtils工具类
下面说一下商品查询的完整思路
首先,我们分析一下页面需要的数据
再去看一下spu表
也就是在数据库中查询的字段就只有id,title在spu表里面存在,那么我们这里就想着在开发中间去扩展一下这个对象
去对外接口模块里面添加一个supbo对象,生产开发中产生的对象,临时对象,单独放一个包
我发现我还没创建sup对象,我先把sup这个对象给创建出来
java
package com.leyou.item.pojo;
import lombok.Data;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Data
@Table(name = "tb_spu")
public class Sup {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long brandId;
private Long cid1;
private Long cid2;
private Long cid3;
private String title;// 标题
private String subTitle;// 子标题
private Boolean saleable;// 是否上架
private Boolean valid;// 是否有效,逻辑删除用
private Date createTime;// 创建时间
private Date lastUpdateTime;// 最后修改时间
}
下面在去创建SpuBo对象,这个对象需要继承它,然后新增两个查询字段
目前来讲,我们只需要一下的值
上面就把我们额外需要的一个对象创建出来了,下面我们把这个查询对象需要的mapper给创建出来
下面去写controller层面的代码
我们先去拿到请求路径
这个是操作商品相关的内容,所以控制器就直接是GoodsController,我们去新创建一下这个控制器
下面我们去goodsService里面实现业务方法
在完整的代码展示之前,我先贴上在过程中,我们需要调用的方法
在我们查询三集分类的分类名称的时候,我们要去categoryService里面去实现下面这个方法
这个方法是调用了通用Mapper的一个根据一个List结合来返回相应数据的Lis集合的这样一个方法
但这个方法有一个要求就是,你的通用Mapper必须继承下面这个类
问题报错来了
因为上面我怎么改都有点问题,于是我把这些步骤拆分了出来
下面在继续去写主干代码
这里贴上主干代码
GoodsController
java
package com.leyou.item.web;
import com.leyou.common.pojo.PageResult;
import com.leyou.item.bo.SpuBo;
import com.leyou.item.service.GoodsService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GoodsController {
@Autowired
private GoodsService goodsService;
//下面我们要做的是通过分页查询商品信息
@GetMapping("/spu/page")
public ResponseEntity<PageResult<SpuBo>> querySpuBoByPage (
@RequestParam(value = "page", defaultValue = "1") Integer page,
@RequestParam(value = "rows", defaultValue = "5") Integer rows,
@RequestParam(value = "key",required = false) String key,
@RequestParam(value = "saleable", required = false) Boolean saleable
) {
//分页查询spu信息
PageResult<SpuBo> result = goodsService.querySpuByPageAndSort(page, rows, key, saleable);
if (result == null || result.getItems().size() == 0) {
return ResponseEntity.notFound().build();
}
//正常返回创建成功
return ResponseEntity.ok(result);
}
}
下面贴上业务层的代码
GoodsService
java
package com.leyou.item.service;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.leyou.common.pojo.PageResult;
import com.leyou.item.bo.SpuBo;
import com.leyou.item.mapper.BrandMapper;
import com.leyou.item.mapper.SpuMapper;
import com.leyou.item.pojo.Brand;
import com.leyou.item.pojo.Spu;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import tk.mybatis.mapper.entity.Example;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;
@Service
public class GoodsService {
//先不考虑引入什么样的mapper
//要查spu,所以要引入spu的mapper对象
@Autowired
private SpuMapper spuMapper;
//我们在想找分类字段的时候,我们必须用到CategoryService里面相应的通过id找到分类的方法
@Autowired
private CategoryService categoryService;
//我们在考虑通过spu里面的商品id去找,直接引入mapper一键搞定
@Autowired
private BrandMapper brandMapper;
public PageResult<SpuBo> querySpuByPageAndSort(Integer page, Integer rows, String key, Boolean saleable) {
//这里是查询spu
//返回一个带有分页的结果集
PageHelper.startPage(page,rows);//这个分页会拼到后面的所有sql语句里面
//我们利用通用Mapper来查,我们要设置通用条件
Example example = new Example(Spu.class);
Example.Criteria criteria = example.createCriteria();
//查询上下架
if (saleable != null) {
criteria.orEqualTo("saleable",saleable);
}
//模糊查询如果有key的话
if (StringUtils.isNotBlank(key)) {
criteria.andLike("title","%" + key + "%");
}
//下面通过spuMapper查Spu对象,这个需要一个集合类型的对象
//通过上面的
Page<Spu> pageInfo = (Page<Spu>) spuMapper.selectByExample(example);
//上面是得到了一个spu,我们的目的是得到一个List<SpuBo>
List<SpuBo> list = pageInfo.getResult().stream().map(spu -> {
//处理每一个查寻到的spu对象
//我们要把spu对象变成SpuBo对象
SpuBo spuBo = new SpuBo();//每个对象一进来都会实例一个这个对象
//我们把每一个spu的属性给拷贝一下
BeanUtils.copyProperties(spu,spuBo);
//下面考虑一下问题:1.我们的cname怎么取,也就是这个spu关联的分类
//怎么取?是不是要通过spu里面的cid来取,这是个List<Category>
//我们要把它变成字符串集合对象打印
//这个是category表里面的操作,调用categoryService里面的具体方法
//这里面针对这个品牌来讲是三级目录
List<String> names = categoryService.queryNameByIds(Arrays.asList(spu.getCid1(),spu.getCid2(),spu.getCid3()));
//上面我们就先去把这个queryNameByIds这个方法给写出来
//上面就查询到了商品的所属分类
//把分类赋值给Bo对象
spuBo.setCname(StringUtils.join(names,"/"));//格式化一下
//上面查了分类,下面就要去查找品牌
//品牌的查找我们就考虑要引入品牌的servcie还是mapper
//考虑到这里如果mapper能一下给我们找出来,就直接mapper
//如果不能一下找出来,还要经过业务处理,那么就走service
//这里我们通过Brand的Mapper中的主键查询就能找出来,并且没有业务转换,直接引入mapper
Brand brand = brandMapper.selectByPrimaryKey(spu.getBrandId());
spuBo.setBname(brand.getName());
return spuBo;
}).collect(Collectors.toList());//变成了List<spuBo>这样一个集合对象
//注意要给前端返回一个分页对象,并把结果给带过去
//现在就是上面的结果集数据全部都封装到了spuBo这个对象里面
return new PageResult<>(pageInfo.getTotal(),list);
}
}
然后查看展示效果