商品规格与数据结构


下面来说一下数据库的设计与分析
其实对于spu这张表来说,大体设计还是比较好设计的

看一下下面这张图是一个产品的规格参数

上面主体就是一个规格参数,基本信息又是一个规格参数
这里就是涉及到了一个商品的具体信息,sku就是一个具体带有了规格参数的商品

先不管上面表设计的具体细节,先来看下面一张图

那我们在来分析几张表与表之间的关系

下面对上面的关系进行拓展,先来看下面几个页面

上面这张图就是我们要用于搜索的规格参数,就是哪些规格参数要拿上来作为参数条件

上面这张图就是讲这些规格参数,都是从什么类型的表上获取数据的方式
有了上面的信息,下面我去看一下上面这些表在数据中的具体信息
先来看分类表

这张表创建了一个相关的分类信息,这张表重点关注parent_id与is_parent这两个字段
下面用一张图来解释这个目录结构

下面在讲解一下sort这个排序指数,越小越靠前

下面我们说一下一般在数据库里面我们都怎样进行查询
1.比如我们查出所有的顶级父类,也就是parent_id = 0 的所有分类

2.下面我们做一个查询是通过某一个父子类,去查找到它下面的所有直接的直接子类,比如下面这样查询

比如点击图书、音像、电子书刊就能找到电子书刊和音像这两个直接子类
首先分析一下,父类与它的直接子类关联的字段就是parent_id,也就是在一棵树中,同级之间的兄弟子类的parent_id都是一样的,于是我们可以进行下面的sql语句查询

如果还要继续深入的往子类与子类的查找,还是查找当前结点的parent_id,然后根据这个parent_id来查找它的直接子类
分类表看了之后,我们来看参数数组表
这个表我们还没有被创建出来,在创建之前,我们先来看一下这张表与tb_category表之间的关系
先用习惯思维考虑一下,一个分类下面肯定有多个参数组的信息,
我们进入淘宝,看一下手机-》华为手机拥有的规格参数信息

上面一个手机分类下面就有很多不同的参数组 ,比如说有基本信息组,有屏幕组,有网络组

下面我们去看一下tb_spec_grop这张表
我们在数据库中还没有构建出来,我们先去把这张表给构建出来

然后现在我们向这个表里面插入一些数据
类似于如下

对于插入做一个简单的数据分析

这个时候我们又要来说到另外一张表
这张表就是规格参数组表的每一个组对应的具体的规格属性,这个可以这样来理解,就是参数组表里面存放的是表头,具体的规格参数名是放在这个规格参数表名里面,这张表的名字是tb_spec_param(明显与参数数组表是一个一(tb_spec_group)对多(tb_spec_param)的关系)
下面我们把这张具体的参数表给创建出来

上面简单分析一下重点字段的得来

上面cid是因为一个分类下面会从sku里面或者spu里面抽取很多参数属性来作为搜索部分的条件
比如下面

其中gid的说明:参数组与参数表是一个1对多的关系,在多的一方肯定引入参数组的id,然后两者才能相互关联
其中generic字段的说明:是否是spu通用属性,意思就是这个属性是不是放到大家都能查找到的部分,也就是规格参数里面共同的部分,比如主体信息,这个字段的重要性在于如果它是ture,也就是通用属性,我们要去spu里面参数值,如果不是通用部分,我们要去sku里面找值
上面就是创建了这张表,下面我们去插入这张表的具体数据
类似于下面这些值

下面我们可以去看一下这张表里面保存的数据信息

下面我们说一下规格参数组页面分析
这里我们要实现点击一个分类之后,他会拿出一个参数组,同时我们还可以拿到这个参数组对应的参数列表
情况如下

其实最后点击啊落到的位置就是一个分类树你下面非父结点的位置,他下面才是保存真正的商品,也就是一个分类树下面,这个节点is_parent = 0 ,他下面就是保存了具体的商品
下面继续分析这个页面布局

上面这是初始页面
我们如果一直点,然后点到最后,他会提示

因为规格是与商品分类绑定的,因此首先会展示商品分类树,提示你要选择的商品分类,才能看到规格参数模板,下面看一下页面实现
这个页面我们简单去了解一下前端

下面就是右侧部分,我们规格参数展示的效果

下面就是展示具体的参数数据
下面这些内容都可以大概的看一看
下面的规格参数组和参数信息都是我们自己定义的组件,去找一下这个组件


找到页面中的点击事件,这个就是我们要清楚页面是怎么发的
在获得分类之后我们有一个点击事件

上面就是handleClick这个点击事件

这个点击事件,我们就可以去获取到这个节点一些信息,并且给它封装到了currentNode这个对象里面

下面我们具体展示规格参数组的组件SpecGroup.vue

上面就会发送一个请求,根据分类来找到参数组

有了这样一个接口之后,我们就可以去写页面
请求方式:get 请求参数是一个cid,请求路径是/spec/groups 返回的是什么?
分析一下这里是一个表
也没有进行分页,所以大概率情况下是返回一个List集合,里面保存的是一个参数规格组的对象
下面就可以去实现这部分的代码

下面看tb_spec_group这个表关联的类SpecGroup
java
package com.leyou.item.pojo;
import javax.persistence.*;
@Table(name = "tb_spec_group")
public class SpecGroup {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;//自己的主键id
private Long cid;//里面维护一个分类id,利用这个id可以找到参数组头
//我们需要查询一个分组下面的规格参数
//这个spec_group表与spec_param表是一个1对多的关系
//所以我们放一个Spec_param的集合对象
//这个是1的一方,引入集合个多的一方
@Transient
private List<SpecParam> params;
}
下面去做tb_spec_param这张表关联的类SpecParam
java
package com.leyou.item.pojo;
import javax.persistence.*;
@Table(name="tb_spec_param")
public class SpecParam {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long cid;
private Long groupId;
private String name;
@Column(name = "`numeric`")
private Boolean numeric;
private String unit;
private Boolean generic;
private Boolean searching;
private String segments;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCid() {
return cid;
}
public void setCid(Long cid) {
this.cid = cid;
}
public Long getGroupId() {
return groupId;
}
public void setGroupId(Long groupId) {
this.groupId = groupId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getNumeric() {
return numeric;
}
public void setNumeric(Boolean numeric) {
this.numeric = numeric;
}
public String getUnit() {
return unit;
}
public void setUnit(String unit) {
this.unit = unit;
}
public Boolean getGeneric() {
return generic;
}
public void setGeneric(Boolean generic) {
this.generic = generic;
}
public Boolean getSearching() {
return searching;
}
public void setSearching(Boolean searching) {
this.searching = searching;
}
public String getSegments() {
return segments;
}
public void setSegments(String segments) {
this.segments = segments;
}
}

这里有个@Column注解的作用是区分数据库字段这里加上``反引号,是因为numeric这个字段是mysql里面的关键字
下面我们要去编写我们的业务
先去完成mapper这个接口
package com.leyou.item.mapper;
import com.leyou.item.pojo.SpecGroup;
import tk.mybatis.mapper.common.Mapper;
public interface SpecGroupMapper extends Mapper<SpecGroup> {
}
上面继承了通用mapper,下面我们去写Controller,写Controller之前,我们先分析一下下面需要的东西,在页面的ajax请求中可以看出
在来说一下这里的Mapper,这里是SpecGroupMapper,为什么不是SpecificationMapper,因为一张表对应一个Mapper,对于规格参数来说,有两个这样类,一个是SpecGroup,一个是SpecParam
SpecificationController.java
java
package com.leyou.item.web;
import com.leyou.item.pojo.SpecGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@RestController
@RequestMapping("spec")
public class SpecificationController {
@Autowired
private SpecificationService specificationService;
@GetMapping("groups/{cid}")
public ResponseEntity<List<SpecGroup>> queryGroupsByCid(@PathVariable("cid") Long cid) {
List<SpecGroup> groups = this.specificationService.queryGroupsByCid(cid);
if(CollectionUtils.isEmpty(groups)) {
//如果为空
//直接构建一个404的状态码
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(groups);
//另外一种写法
// return ResponseEntity.status(HttpStatus.OK).body(groups);
}
}
上面有一个工具类CollectionUtils,这个是spring给我们提供的来处理集合


下面我们又要去完成service里面的方法
java
package com.leyou.item.service;
import com.leyou.item.mapper.SpecGroupMapper;
import com.leyou.item.pojo.SpecGroup;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SpecificationService {
@Autowired
private SpecGroupMapper groupMapper;
public List<SpecGroup> queryGroupsByCid(Long cid) {
//利用通用mapper中的select完成查询工作,需要传入一个查询的实体类
SpecGroup specGroup = new SpecGroup();
specGroup.setCid(cid);
return this.groupMapper.select(specGroup);
}
}
下面我们进行一个页面访问测试
重新启动一下ly-item这个微服务

组名没有被查询到
检查了一下代码,发现

根本没有名字给我们返回,原来在下面我忘了加上name这个属性字段
加上重启微服务,同时要注意给这个字段加上set与get方法

上面就完成了参数组的查询操作
下面要去做规格参数的查询

我们可不可以直接找到这个接口
这个接口路径已经被拿到了
按照惯例我们先去分析一下前端页面
这里是点击规格组的时候,就会切换到规格参数的一个显示,那么我们判定肯定是在规格组中绑定了点击事件

下面就会去触发这个方法

这个方法作用是

回到我们的父组件,也就是Specification.vue这个组件
<spec-group :cid="currentNode.id" v-show="showGroup" @select="selectGroup"/>

事件处理函数


下面分析一下页面查询规格参数

下面我们去实现代码
先来写上Mapper这个类

然后我们来写我们的Controller类
这个都是规格参数相关的操作,所以用一个SpecificationController类
把请求路径贴过来

方式get,路径是/spec/params
参数是:gid,也就是分组id
返回结果:该分组下的规格参数集合List<SpecParam>
对一这个返回结果来多说一句
一个分类下面的的参数组里面,每一个标题参数对应很多子类参数信息

上面这个主体里面,可能会有多条的记录信息,也就是有多个SpecParam对象
下面我们去完成代码
还是先去写我们的Mapper

再去写我们的service层SpecificationService,这个service还是用之前的SpecificationService,只不过这次使用的Mapper是SpecParamMapper paramMapper
java
package com.leyou.item.service;
import com.leyou.item.mapper.SpecGroupMapper;
import com.leyou.item.mapper.SpecParamMapper;
import com.leyou.item.pojo.SpecGroup;
import com.leyou.item.pojo.SpecParam;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class SpecificationService {
@Autowired
private SpecGroupMapper groupMapper;
@Autowired
private SpecParamMapper paramMapper;
public List<SpecGroup> queryGroupsByCid(Long cid) {
//利用通用mapper中的select完成查询工作,需要传入一个查询的实体类
SpecGroup specGroup = new SpecGroup();
specGroup.setCid(cid);
return this.groupMapper.select(specGroup);
}
//之类就是通过参数组的gid来查询参数
//这里考虑用通用Mapper的select,内部传入一个要返回的集合对象
public List<SpecParam> queryParams(Long gid) {
SpecParam param = new SpecParam();
param.setGroupId(gid);
return paramMapper.select(param);
}
}
下面再去写我们的控制层面的代码
它的请求路径我们可以再去看一下

返回的结果就是一个存放很多记录对象的集合,List<SpecParam>
java
package com.leyou.item.web;
import com.leyou.item.pojo.SpecGroup;
import com.leyou.item.pojo.SpecParam;
import com.leyou.item.service.SpecificationService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.util.CollectionUtils;
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("spec")
public class SpecificationController {
@Autowired
private SpecificationService specificationService;
@GetMapping("groups/{cid}")
public ResponseEntity<List<SpecGroup>> queryGroupsByCid(@PathVariable("cid") Long cid) {
List<SpecGroup> groups = this.specificationService.queryGroupsByCid(cid);
if(CollectionUtils.isEmpty(groups)) {
//如果为空
//直接构建一个404的状态码
return ResponseEntity.notFound().build();
}
return ResponseEntity.ok(groups);
//另外一种写法
// return ResponseEntity.status(HttpStatus.OK).body(groups);
}
//根据参数组的gid查询规格参数
public ResponseEntity<List<SpecParam>> queryParams(@RequestParam("gid")Long gid) {
List<SpecParam> params = specificationService.queryParams(gid);
//对于集合我们通常要做一些安全性检查的判定
if(CollectionUtils.isEmpty(params)) {
return ResponseEntity.notFound().build();//直接构建一个404的返回状态码
}
return ResponseEntity.ok(params);
}
}
下面重启商品微服务模块,测试
注意把控制器这里的注解加上

测试一下 ,我们点击了主体这个参数组名,下面就会有很多条记录对象

好了,先写到这,祝大家早安午安晚安。