乐优商城(04)--商品规格
乐优商城(04)–商品规格
一、商品规格数据结构
乐优商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU,了解一下:
1.1.SPU和SKU
SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集
SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品
可以看出:
- SPU是一个抽象的商品集概念,为了方便后台的管理。
- SKU才是具体要销售的商品,每一个SKU的价格、库存可能会不一样,用户购买的是SKU而不是SPU
1.2、数据库表设计分析
数据库中关于商品规格的表有五个即:
tb_spu表分析
以上面华为手机商品为例,头部会显示分类和品牌,所以需要该商品所属的三个级别的分类id以及品牌id,
每个商品都会有标题和副标题,SPU是一个抽象的商品集概念,是为了方便后台的管理的,所以需要该商品的上下架的状态修改,以及创建时间和最后修改时间
这里是做了表的垂直拆分,将SPU的详情放到了另一张表:tb_spu_detail
tb_spu_detail这张表中的数据都比较大,为了不影响主表的查询效率所以拆分出这张表。
tb_spu_detail表分析
所以包含的字段有
description:描述
specification:规格
packaging_list:包装
after_service:售后服务
comment:评价
评价的数据量很庞大且数据价值很低,若存储在mysql中性能很不好,后面使用mongoDB存储
一个分类下的所有SPU具有类似的规格参数。SPU下的SKU可能会有不同的规格参数信息,因此:
- SPUDetail中保存通用的规格参数信息。
- SKU中保存特有规格参数。
需要注意的是这两个字段:generic_spec和special_spec。
generic_spec字段
其中保存通用规格参数信息的值,为了方便查询,使用json格式
- key:对应的规格参数的
spec_param
的id - value:对应规格参数的值
- key:对应的规格参数的
special_spec字段
以手机为例,品牌、操作系统等肯定是全局通用属性,内存、颜色等肯定是特有属性。
当确定了一个SPU,比如小米的:小米11
全局属性值都是固定的了:
品牌:小米 型号:小米11
特有属性举例:
颜色:[香槟金, 樱花粉, 磨砂黑] 内存:[6G, 8G] 机身存储:[56GB, 128GB, 256GB]
颜色、内存、机身存储,作为SKU特有属性,key虽然一样,但是SPU下的每一个SKU,其值都不一样,所以值会有很多,形成数组。
在SPU中,会把特有属性的所有值都记录下来,形成一个数组,也是json结构:
- key:规格参数id
- value:spu属性的数组
之所以在spu中也记录一份特有规格参数,是因为有时候需要把所有规格参数都查询出来,而不是只查询1个sku的属性。比如,商品详情页展示可选的规格参数时
tb_sku表分析
不同的商品分类,属性是不同,比如手机有内存,衣服有尺码,所以很难设计在同一张表中
但是注意SKU是具体的商品信息,也是有一些单个属性的
即:与spu关联、标题、产品图片、价格、特有属性
SKU的特有属性是商品规格参数的一部分:
这样规格参数中的属性可以标记成两部分:
- spu下所有sku共享的规格属性(称为全局属性)
- 每个sku不同的规格属性(称为特有属性)
查看搜索面板:
不难发现很多过滤条件在规格参数中都能找到,规格参数中的数据,将来会有一部分作为搜索条件来使用。所以在设计时,将这部分属性标记出来,将来做搜索的时候,作为过滤条件。要注意的是,无论是SPU的全局属性,还是SKU的特有属性,都有可能作为搜索过滤条件的,并不冲突,而是有一个交集:
indexes字段
保存的是特有属性待选项的下标组合,中间用’_'连接
拿上面的产品图为例:
选中了:羽墨黑,4GB+128GB,官方标配,优惠套餐2
indexes字段内容为:0_0_0_1
这样设计当用户点击选中一个特有属性,就能根据角标快速定位到sku。
tb_spec_group
可以看到规格参数是分组的,每一组都有多个参数键值对。不过对于规格参数的模板而言,其值现在是不确定的,不同的商品值肯定不同,模板中只要保存组信息、组内参数信息即可。
因此设计了两张表:
- tb_spec_group:组,与商品分类关联
- tb_spec_param:参数名,与组关联,一对多
该tb_spec_group
表中有三个字段:
- id:主键
- cid:商品分类id,一个分类下有多个模板
- name:该规格组的名称。
tb_spec_param
通用属性
用一个布尔类型字段来标记是否为通用:
- generic来标记是否为通用属性:
- true:代表通用属性
- false:代表sku特有属性
- generic来标记是否为通用属性:
搜索过滤
与搜索相关的有两个字段:
- searching:标记是否用作过滤
- true:用于过滤搜索
- false:不用于过滤
- segments:某些数值类型的参数,在搜索时需要按区间划分,这里提前确定好划分区间
- 比如电池容量,02000mAh,2000mAh3000mAh,3000mAh~4000mAh
- searching:标记是否用作过滤
数值类型
某些规格参数可能为数值类型,这样的数据才需要划分区间,我们有两个字段来描述:
- numberic:是否为数值类型
- true:数值类型
- false:不是数值类型
- unit:参数的单位
- numberic:是否为数值类型
二、商品规格管理
2.1、页面布局
打开规格参数页面,看到如下内容:
因为规格是跟商品分类绑定的,因此首先会展现商品分类树,并且提示你要选择商品分类,才能看到规格参数的模板。一起了解下页面的实现:
点击该页面,可以发现这里使用了v-layout
来完成页面布局,并且添加了row属性,代表接下来的内容是行布局(左右)。
可以看出页面分成2个部分:
<v-flex xs3>
:左侧,内部又分上下两部分:商品分类树及标题v-card-title
:标题部分,这里是提示信息,告诉用户要先选择分类,才能看到模板v-tree
:这里用到的是我们之前讲过的树组件,展示商品分类树,
<v-flex xs9 class="px-1">
:右侧:内部是规格参数展示
2.2、右侧规格
右侧规格最终效果:
可以看到右侧分为上下两部分:
- 上部:面包屑,显示当前选中的分类
- 下部:table,显示规格参数信息
页面实现:
这是一个spec-group
组件(规格组)和spec-param
组件(规格参数),这是提前定义的独立组件。在SpecGroup中定义了表格。
2.3、规格组的查询
2.3.1、前端页面
当点击树节点时,要将v-dialog
打开,因此必须绑定一个点击事件:(Specification.vue)
来看下handleClick
方法:(Specification.vue)
点击事件发生时,发生了两件事:
- 记录当前选中的节点,选中的就是商品分类
showGroup
被置为true,则规格组就会显示了。
同时,把被选中的节点(商品分类)的id传递给了SpecGroup
组件:(Specification.vue)
来看下SpecGroup.vue
中的实现:
查看页面控制台,可以看到请求已经发出:
2.3.1、后端实现
实体类
在leyou-item-interface
中添加实体类:
@Table(name = "tb_spec_param")
public class SpecParam {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id; //主键private Long cid; //商品分类idprivate Long groupId; //分组idprivate String name; //参数名@Column(name = "`numeric`") //该字段在数据库中为关键字,需特殊处理private Boolean numeric; //是否是数字类型参数private String unit; //数字类型参数的单位private Boolean generic; //是否是通用参数private Boolean searching; //是否可用于查询private String segments; //若数值类型为搜素,需添加一个数字范围//get和set方法
}
@Table(name = "tb_spec_group")
public class SpecGroup {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id; //主键private Long cid; //分类id 一个分类下会有多个规格组private String name; //规格组的名称@Transientprivate List<SpecParam> params; //参数集合//get和set方法
}
mapper
/*** SpecGroup 通用mapper*/
public interface SpecGroupMapper extends Mapper<SpecGroup> {}
这里将SpecGroup和SpecParam两个放在一起实现
controller
- 请求方式:get
- 请求路径:/spec/groups/{cid} ,这里通过路径占位符传递商品分类的id
- 请求参数:商品分类id
- 返回结果:页面是直接把
resp.data
赋值给了groups:
@RestController
@RequestMapping("/spec")
public class SpecificationController {@Autowiredprivate SpecificationService specificationService;/*** 根据分类id查询参数组* @param cid* @return*/@GetMapping("/groups/{cid}")public ResponseEntity<List<SpecGroup>> queryGroupsByCid(@PathVariable("cid") Long cid){List<SpecGroup> specGroups = this.specificationService.queryGroupsByCid(cid);if (CollectionUtils.isEmpty(specGroups)){return ResponseEntity.notFound().build();}return ResponseEntity.ok(specGroups);}
}
service
public interface SpecificationService {/*** 根据分类id查询参数组* @param cid* @return*/List<SpecGroup> queryGroupsByCid(Long cid);
}
实现类:
@Service
public class SpecificationServiceImpl implements SpecificationService {@Autowiredprivate SpecGroupMapper specGroupMapper;/*** 根据分类id查询参数组** @param cid* @return*/@Overridepublic List<SpecGroup> queryGroupsByCid(Long cid) {SpecGroup specGroup = new SpecGroup();specGroup.setCid(cid);return this.specGroupMapper.select(specGroup);}
}
2.4、规格参数查询
2.4.1、前端页面
表格切换:
当点击规格组,会切换到规格参数显示,肯定是在规格组中绑定了点击事件:
看下事件处理:
可以看到这里是使用了父子通信,子组件触发了select事件.
再来看下父组件的事件绑定:
事件处理:
这里记录了选中的分组,并且把标记设置为false,这样规格组就不显示了,而是显示:SpecParam
并且,把group也传递到spec-param
组件:
来看SpecParam.vue
的实现:
查看页面控制台,发现请求已经发出:
2.4.2、后端实现
mapper
/*** SpecParam 的通用mapper*/
public interface SpecParamMapper extends Mapper<SpecParam> {}
controller
- 请求方式:GET
- 请求路径:/spec/params
- 请求参数:gid,分组id
- 返回结果:该分组下的规格参数集合
List<SpecParam>
/*** 根据gid查询具体参数* @param gid* @return*/
@GetMapping("/params")
public ResponseEntity<List<SpecParam>> queryParamsByGid(@RequestParam("gid") Long gid){List<SpecParam> specParams = this.specificationService.queryParamsByGid(gid);if (CollectionUtils.isEmpty(specParams)){return ResponseEntity.notFound().build();}return ResponseEntity.ok(specParams);
}
service
/*** 根据gid查询具体参数* @param gid* @return*/
List<SpecParam> queryParamsByGid(Long gid);
实现类:
@Autowired
private SpecParamMapper specParamMapper;/*** 根据gid查询具体参数** @param gid* @return*/
@Override
public List<SpecParam> queryParamsByGid(Long gid) {SpecParam specParam = new SpecParam();specParam.setGroupId(gid);return this.specParamMapper.select(specParam);
}
2.5、规格参数组增、删、改
2.5.1、前端页面
点击新增分组按钮,请求已发:
查看前端页面代码:
2.5.2、后端实现
增加:
controller
- 请求方式:POST
- 请求路径:/item/spec/group
- 请求参数:specGroup
- 返回结果:无,响应状态码为201
/*** 添加一个参数分组* @param specGroup* @return*/
@PostMapping("/group")
public ResponseEntity<Void> addSpecGroup(@RequestBody SpecGroup specGroup){this.specificationService.addSpecGroup(specGroup);return ResponseEntity.status(HttpStatus.CREATED).build();
}
service
/*** 添加一个参数分组* @param specGroup* @return*/
void addSpecGroup(SpecGroup specGroup);
实现类:
/*** 添加一个参数分组** @param specGroup* @return*/
@Override
public void addSpecGroup(SpecGroup specGroup) {this.specGroupMapper.insert(specGroup);
}
修改:
controller
- 请求方式:PUT
- 请求路径:/item/spec/group
- 请求参数:specGroup
- 返回结果:无,响应状态码为202
/*** 修改一个参数分组* @param specGroup* @return*/@PutMapping("/group")public ResponseEntity<Void> updateSpecGroup(@RequestBody SpecGroup specGroup){this.specificationService.updateSpecGroup(specGroup);return ResponseEntity.status(HttpStatus.ACCEPTED).build();}
service
/*** 修改一个参数分组* @param specGroup* @return*/
void updateSpecGroup(SpecGroup specGroup);
实现类
/*** 修改一个参数分组** @param specGroup* @return*/
@Override
public void updateSpecGroup(SpecGroup specGroup) {this.specGroupMapper.updateByPrimaryKeySelective(specGroup);
}
删除:
controller
- 请求方式:POST
- 请求路径:/item/spec/group
- 请求参数:id,即参数组的id
- 返回结果:无,响应状态码为200
/*** 根据组id删除参数组* @param gid* @return*/
@DeleteMapping("/group/{id}")
public ResponseEntity<Void> deleteSpecGroup(@PathVariable("id") Long gid){this.specificationService.deleteSpecGroup(gid);return ResponseEntity.ok().build();
}
service
/*** 根据组id删除参数组* @param gid* @return*/
void deleteSpecGroup(Long gid);
实现类:
/*** 根据组id删除参数组** @param gid* @return*/
@Override
@Transactional
public void deleteSpecGroup(Long gid) {//先查询该参数组内的参数List<SpecParam> specParams = queryParamsByGid(gid);//如果组内有数据if (specParams.size() > 0){//删除组内的具体参数specParams.forEach(specParam -> this.specParamMapper.deleteByPrimaryKey(specParam));}//再删除参数组this.specGroupMapper.deleteByPrimaryKey(gid);
}
2.6、规格参数的增、删、改
2.6.1、前端页面
点即新增参数,查看表单和发送的请求
查看前端页面代码:
2.6.2、后端实现
新增:
controller
- 请求方式:POST
- 请求路径:/item/spec/group
- 请求参数:SpecParam
- 返回结果:无,响应状态码为201
/*** 添加一个参数* @param specParam* @return*/
@PostMapping("/param")
public ResponseEntity<Void> addSpecParam(@RequestBody SpecParam specParam){this.specificationService.addSpecParam(specParam);return ResponseEntity.status(HttpStatus.CREATED).build();
}
service
/*** 添加一个参数* @param specParam* @return*/
void addSpecParam(SpecParam specParam);
实现类:
/*** 添加一个参数** @param specParam* @return*/
@Override
public void addSpecParam(SpecParam specParam) {this.specParamMapper.insert(specParam);
}
修改:
controller
- 请求方式:PUT
- 请求路径:/item/spec/group
- 请求参数:SpecParam
- 返回结果:无,响应状态码为202
/*** 修改一个参数* @param specParam* @return*/
@PutMapping("/param")
public ResponseEntity<Void> updateSpecParam(@RequestBody SpecParam specParam){this.specificationService.updateSpecParam(specParam);return ResponseEntity.status(HttpStatus.ACCEPTED).build();
}
service
/*** 修改一个参数* @param specParam* @return*/
void updateSpecParam(SpecParam specParam);
实现类:
/*** 修改一个参数** @param specParam* @return*/
@Override
public void updateSpecParam(SpecParam specParam) {this.specParamMapper.updateByPrimaryKeySelective(specParam);
}
删除:
controller
- 请求方式:DELETE
- 请求路径:/item/spec/group
- 请求参数:id,即参数的id
- 返回结果:无,响应状态码为202
/*** 根据参数的id删除参数* @param id* @return*/
@DeleteMapping("/param/{id}")
public ResponseEntity<Void> deleteSpecParam(@PathVariable("id") Long id){this.specificationService.deleteSpecParam(id);return ResponseEntity.ok().build();
}
service
/*** 根据参数的id删除参数* @param id* @return*/
void deleteSpecParam(Long id);
实现类:
/*** 根据参数的id删除参数** @param id* @return*/
@Override
public void deleteSpecParam(Long id) {this.specParamMapper.deleteByPrimaryKey(id);
}
乐优商城(04)--商品规格相关推荐
- 乐优商城day13(商品详情页,rabbitMQ安装)
所有代码发布在 [https://github.com/hades0525/leyou] Day13(rabbitmq) 2019年2月13日 14:45 使用thymeleaf thymeleaf基 ...
- 乐优商城(四)商品规格管理
文章目录 1. 商品规格 1.1 SPU 和 SKU 1.2 分析商品规格的关系 1.3 数据库设计 1.3.1 商品规格组表 1.3.2 商品规格参数表 2. 商品规格组 2.1 商品规格组前端 2 ...
- 【javaWeb微服务架构项目——乐优商城day05】——商品规格参数管理(增、删、改,查已完成),SPU和SKU数据结构,商品查询
乐优商城day05 0.学习目标 1.商品规格数据结构 1.1.SPU和SKU 1.2.数据库设计分析 1.2.1.思考并发现问题 1.2.2.分析规格参数 1.2.3.SKU的特有属性 1.2.4. ...
- 乐优商城学习笔记五-商品规格管理
0.学习目标 了解商品规格数据结构设计思路 实现商品规格查询 了解SPU和SKU数据结构设计思路 实现商品查询 了解商品新增的页面实现 独立编写商品新增后台功能 1.商品规格数据结构 乐优商城是一个全 ...
- 乐优商城(05)--商品管理
乐优商城(05)–商品管理 一.导入图片资源 现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,因此需要把图片导入到服务器中: 将images.zip文件上传至/leyou/static目录: ...
- 乐优商城笔记六:商品详情页
使用模板引擎 Thymeleaf + nginx 完成商品详情页静态化 完成乐优商城商品详情页 搭建商品详情页微服务 创建子工程 GroupId:com.leyou.service ArtifactI ...
- 乐优商城 Day 09(thymeleaf,Rabbitmq,商品详情页,非教程)
乐优商城学习Day09: 注意:此次代码都是在第八天的基础上 第八天的链接如下: https://blog.csdn.net/zcylxzyh/article/details/100859210 此次 ...
- 乐优商城之项目搭建(四)
文章目录 (一)项目分类 (二)电商行业 (三)专业术语 (四)项目介绍 (五)技术选型 (六)开发环境 (七)搭建后台环境:父工程 (八)搭建后台环境:eureka (九)搭建后台环境:zuul ( ...
- 商城项目-商品规格数据结构
1.商品规格数据结构 乐优商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别.为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU,了解一下: 1.1.SPU和SKU ...
- 学习乐优商城中遇到的坑
本人暑假期间学习了乐优商城这个项目,历时二十天,遇到了许多的麻烦,应该会有挺多人遇到.所以想记录一下,让大家跳坑. 首先,我建议jar包的版本,都选择和老师的一样,不然会出现一些莫名奇妙的报错. 在本 ...
最新文章
- MIT-THU未来城市创新网络即将和你见面!
- Application,Session和Cookies的区别
- Linux有问必答:如何检查Linux的内存使用状况
- 怎样将html转换do,html代码格式化 Reddo的教程
- CodeAbstract
- Github Actions:再次改变软件开发
- SpringBoot应用部署[转]
- 为GitHub项目加入Travis-CI的自动集成
- 建设自己拥有的B2C网站一般需要什么样的插件以及大体花费
- .php on line 0,控制台运行php报错 undefined symbol: gdImageCreateFromJpeg in Unknown on line 0 解决方法...
- CSS-----盒子模型
- 部署ganglia3.7
- 备课手记:把Ken Olsen换成姚明
- 马云的教、马云的会、马云的墓
- php调用手写板,手写板使用起来方便、快捷 其原理你知道吗?
- MySQL高级部分理论知识细讲
- /sdcard目录详解
- 第十五篇,man手册
- 袁春风老师:计算机系统基础(一) 第一章
- 日本味之素EB21二丁基乙基己酰基谷氨酸酰胺型胶凝化剂TDS产品说明书