电商项目——商品规格管理
商品规格管理
商品规格数据结构
淘淘商城是一个全品类的电商网站,因此商品的种类繁多,每一件商品,其属性又有差别。为了更准确描述商品及细分差别,抽象出两个概念:SPU和SKU
SPU和SKU
SPU:Standard Product Unit (标准产品单位) ,一组具有共同属性的商品集
SKU:Stock Keeping Unit(库存量单位),SPU商品集因具体特性不同而细分的每个商品
- SPU是一个抽象的商品集概念,为了方便后台的管理。
- SKU才是具体要销售的商品,每一个SKU的价格、库存可能会不一样,用户购买的是SKU而不是SPU
数据库设计分析
商品的规格参数应该是与分类绑定的。每一个分类都有统一的规格参数模板,但不同商品其参数值可能不同。
SKU的特有属性
SPU中会有一些特殊属性,用来区分不同的SKU,我们称为SKU特有属性。如华为META10的颜色、内存属性。
不同种类的商品,一个手机,一个衣服,其SKU属性不相同。
同一种类的商品,比如都是衣服,SKU属性基本是一样的,都是颜色、尺码等。
这样说起来,似乎SKU的特有属性也是与分类相关的?事实上,仔细观察你会发现,SKU的特有属性是商品规格参数的一部分:
也就是说,我们没必要单独对SKU的特有属性进行设计,它可以看做是规格参数中的一部分。这样规格参数中的属性可以标记成两部分:
- 所有sku共享的规格属性(称为全局属性)
- 每个sku不同的规格属性(称为特有属性)
过滤条件中的屏幕尺寸、运行内存、网路、机身内存、电池容量、CPU核数等,在规格参数中都能找到,也就是说,规格参数中的数据,将来会有一部分作为搜索条件来使用。我们可以在设计时,将这部分属性标记出来,将来做搜索的时候,作为过滤条件。要注意的是,无论是SPU的全局属性,还是SKU的特有属性,都有可能作为搜索过滤条件的,并不冲突,而是有一个交集:
规格参数表
整体数据结构
规格参数是分组的,每一组都有多个参数键值对。不过对于规格参数的模板而言,其值现在是不确定的,不同的商品值肯定不同,模板中只要保存组信息、组内参数信息即可。
因此我们设计了两张表:
- tb_spec_group:组,与商品分类关联
- tb_spec_param:参数名,与组关联,一对多
规格组
CREATE TABLE `tb_spec_group` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`cid` bigint(20) NOT NULL COMMENT '商品分类id,一个分类下有多个规格组',`name` varchar(50) NOT NULL COMMENT '规格组的名称',PRIMARY KEY (`id`),KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='规格参数的分组表,每个商品分类下有多个规格参数组';
规格组有3个字段:
- id:主键
- cid:商品分类id,一个分类下有多个模板
- name:该规格组的名称。
规格参数
CREATE TABLE `tb_spec_param` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键',`cid` bigint(20) NOT NULL COMMENT '商品分类id',`group_id` bigint(20) NOT NULL,`name` varchar(255) NOT NULL COMMENT '参数名',`numeric` tinyint(1) NOT NULL COMMENT '是否是数字类型参数,true或false',`unit` varchar(255) DEFAULT '' COMMENT '数字类型参数的单位,非数字类型可以为空',`generic` tinyint(1) NOT NULL COMMENT '是否是sku通用属性,true或false',`searching` tinyint(1) NOT NULL COMMENT '是否用于搜索过滤,true或false',`segments` varchar(1000) DEFAULT '' COMMENT '数值类型参数,如果需要搜索,则添加分段间隔值,如CPU频率间隔:0.5-1.0',PRIMARY KEY (`id`),KEY `key_group` (`group_id`),KEY `key_category` (`cid`)
) ENGINE=InnoDB AUTO_INCREMENT=24 DEFAULT CHARSET=utf8 COMMENT='规格参数组下的参数名';
按道理来说,我们的规格参数就只需要记录参数名、组id、商品分类id即可。但是这里却多出了很多字段,为什么?
还记得我们之前的分析吧,规格参数中有一部分是 SKU的通用属性,一部分是SKU的特有属性,而且其中会有一些将来用作搜索过滤,这些信息都需要标记出来。
通用属性
用一个布尔类型字段来标记是否为通用:
- generic来标记是否为通用属性:
- true:代表通用属性
- false:代表sku特有属性
搜索过滤
与搜索相关的有两个字段:
- searching:标记是否用作过滤
- true:用于过滤搜索
- false:不用于过滤
- segments:某些数值类型的参数,在搜索时需要按区间划分,这里提前确定好划分区间
- 比如电池容量,02000mAh,2000mAh3000mAh,3000mAh~4000mAh
数值类型
某些规格参数可能为数值类型,这样的数据才需要划分区间,我们有两个字段来描述:
- numberic:是否为数值类型
- true:数值类型
- false:不是数值类型
- unit:参数的单位
商品规格参数管理
实体类
在taotao-item-interface
中添加实体类:
@Table(name = "tb_spec_group")
public class SpecGroup {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private Long cid;private String name;// getter和setter省略
}
在taotao-item-service
中编写业务:
mapper
public interface SpecGroupMapper extends Mapper<SpecGroup> {}
controller
先分析下需要的东西,在页面的ajax请求中可以看出:
请求方式:查询,肯定是get
请求路径:/spec/groups/{cid} ,这里通过路径占位符传递商品分类的id
请求参数:商品分类id
返回结果:页面是直接把
resp.data
赋值给了groups:那么我们返回的应该是规格组SpecGroup的集合
代码:
@RestController
@RequestMapping("spec")
public class SpecificationController {@Autowiredprivate SpecificationService specificationService;@GetMapping("groups/{cid}")public ResponseEntity<List<SpecGroup>> querySpecGroups(@PathVariable("cid") Long cid){List<SpecGroup> list = this.specificationService.querySpecGroups(cid);if(list == null || list.size() == 0){return new ResponseEntity<>(HttpStatus.NOT_FOUND);}return ResponseEntity.ok(list);}
}
service:
@Service
public class SpecificationService {@Autowiredprivate SpecGroupMapper specGroupMapper;public List<SpecGroup> querySpecGroups(Long cid) {SpecGroup t = new SpecGroup();t.setCid(cid);return this.specGroupMapper.select(t);}
}
页面查询规格参数
实体类:
@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;// getter和setter ...
}
controller
分析:
- 请求方式:GET
- 请求路径:/spec/params
- 请求参数:gid,分组id
- 返回结果:该分组下的规格参数集合
List<SpecParam>
代码:
@GetMapping("/params")
public ResponseEntity<List<SpecParam>> querySpecParam(@RequestParam(value="gid", required = false) Long gid){List<SpecParam> list =this.specificationService.querySpecParams(gid);if(list == null || list.size() == 0){return new ResponseEntity<>(HttpStatus.NOT_FOUND);}return ResponseEntity.ok(list);}
service
public List<SpecParam> querySpecParams(Long gid){SpecParam t = new SpecParam();t.setGroupId(gid);return this.specParamMapper.select(t);
}
mapper
public interface SpecParamMapper extends Mapper<SpecParam> {}
SPU和SKU数据结构
SPU表
表结构
SPU表:
CREATE TABLE `tb_spu` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'spu id',`title` varchar(255) NOT NULL DEFAULT '' COMMENT '标题',`sub_title` varchar(255) DEFAULT '' COMMENT '子标题',`cid1` bigint(20) NOT NULL COMMENT '1级类目id',`cid2` bigint(20) NOT NULL COMMENT '2级类目id',`cid3` bigint(20) NOT NULL COMMENT '3级类目id',`brand_id` bigint(20) NOT NULL COMMENT '商品所属品牌id',`saleable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否上架,0下架,1上架',`valid` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0已删除,1有效',`create_time` datetime DEFAULT NULL COMMENT '添加时间',`last_update_time` datetime DEFAULT NULL COMMENT '最后修改时间',PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=208 DEFAULT CHARSET=utf8 COMMENT='spu表,该表描述的是一个抽象的商品,比如 iphone8';
与我们前面分析的基本类似,但是似乎少了一些字段,比如商品描述。
我们做了表的垂直拆分,将SPU的详情放到了另一张表:tb_spu_detail
CREATE TABLE `tb_spu_detail` (`spu_id` bigint(20) NOT NULL,`description` text COMMENT '商品描述信息',`generic_spec` varchar(3000) NOT NULL DEFAULT '' COMMENT '通用规格参数数据',`special_spec` varchar(1000) NOT NULL COMMENT '特有规格参数及可选值信息,json格式',`packing_list` varchar(1000) DEFAULT '' COMMENT '包装清单',`after_service` varchar(1000) DEFAULT '' COMMENT '售后服务',PRIMARY KEY (`spu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
这张表中的数据都比较大,为了不影响主表的查询效率我们拆分出这张表。
需要注意的是这两个字段:generic_spec和special_spec。
spu中的规格参数
前面讲过规格参数与商品分类绑定,一个分类下的所有SPU具有类似的规格参数。SPU下的SKU可能会有不同的规格参数,因此我们计划是这样:
- SPU中保存通用的规格参数信息。
- SKU中保存特有规格参数。
generic_spec字段
首先是generic_spec
,其中保存通用规格参数信息的值,这里为了方便查询,使用了json格式
json结构,其中都是键值对:
- key:对应的规格参数的
spec_param
的id - value:对应规格参数的值
special_spec字段
以手机为例,品牌、操作系统等肯定是全局通用属性,内存、颜色等肯定是特有属性。
当你确定了一个SPU,比如小米的:红米4X
全局属性值都是固定的了:
品牌:小米
型号:红米4X
特有属性举例:
颜色:[香槟金, 樱花粉, 磨砂黑]
内存:[2G, 3G]
机身存储:[16GB, 32GB]
颜色、内存、机身存储,作为Sku特有属性,key虽然一样,但是SPU下的每一个sku,其值都不一样,所以值会有很多,形成数组。
我们在SPU中,会把特有属性的所有值都记录下来,形成一个数组:
里面又有哪些内容呢?
来看数据格式:
也是json结构:
- key:规格参数id
- value:spu属性的数组
特有规格参数应该在sku中记录才对,为什么在spu中也要记录一份?
因为我们有时候需要把所有规格参数都查询出来,而不是只查询1个sku的属性。比如,商品详情页展示可选的规格参数时,需展示特有属性Key及特有属性待选项。
SKU表
表结构
CREATE TABLE `tb_sku` (`id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT 'sku id',`spu_id` bigint(20) NOT NULL COMMENT 'spu id',`title` varchar(255) NOT NULL COMMENT '商品标题',`images` varchar(1000) DEFAULT '' COMMENT '商品的图片,多个图片以‘,’分割',`price` bigint(15) NOT NULL DEFAULT '0' COMMENT '销售价格,单位为分',`indexes` varchar(100) COMMENT '特有规格属性在spu属性模板中的对应下标组合',`own_spec` varchar(1000) COMMENT 'sku的特有规格参数,json格式',`enable` tinyint(1) NOT NULL DEFAULT '1' COMMENT '是否有效,0无效,1有效',`create_time` datetime NOT NULL COMMENT '添加时间',`last_update_time` datetime NOT NULL COMMENT '最后修改时间',PRIMARY KEY (`id`),KEY `key_spu_id` (`spu_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='sku表,该表表示具体的商品实体,如黑色的64GB的iphone 8';
还有一张表,代表库存:
CREATE TABLE `tb_stock` (`sku_id` bigint(20) NOT NULL COMMENT '库存对应的商品sku id',`seckill_stock` int(9) DEFAULT '0' COMMENT '可秒杀库存',`seckill_total` int(9) DEFAULT '0' COMMENT '秒杀总数量',`stock` int(9) NOT NULL COMMENT '库存数量',PRIMARY KEY (`sku_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='库存表,代表库存,秒杀库存等信息';
问题:为什么要将库存独立一张表?
因为库存字段写频率较高,而SKU的其它字段以读为主,因此我们将两张表分离,读写不会干扰。
特别需要注意的是sku表中的
indexes
字段和own_spec
字段。sku中应该保存特有规格参数的值,就在这两个字段中。
sku中的特有规格参数
indexes字段
在SPU表中,已经对特有规格参数及可选项进行了保存,结构如下:
{"4": ["香槟金","樱花粉","磨砂黑"],"12": ["2GB","3GB"],"13": ["16GB","32GB"]
}
这些特有属性如果排列组合,会产生12个不同的SKU,而不同的SKU,其属性就是上面备选项中的一个。
比如:
- 红米4X,香槟金,2GB内存,16GB存储
- 红米4X,磨砂黑,2GB内存,32GB存储
你会发现,每一个属性值,对应于SPUoptions数组的一个选项,如果我们记录下角标,就是这样:
- 红米4X,0,0,0
- 红米4X,2,0,1
既然如此,我们是不是可以将不同角标串联起来,作为SPU下不同SKU的标示。这就是我们的indexes字段
这个设计在商品详情页会特别有用:当用户点击选中一个特有属性,你就能根据 角标快速定位到sku。
own_spec字段
看结构:
{"4":"香槟金","12":"2GB","13":"16GB"}
保存的是特有属性的键值对。
SPU中保存的是可选项,但不确定具体的值,而SKU中的保存的就是具体的值。
导入图片信息
现在商品表中虽然有数据,但是所有的图片信息都是无法访问的,我们需要把图片导入到虚拟机:首先,把数据上传到虚拟机下:/taotao/static
目录:
然后,使用命令解压缩:
unzip images.zip
修改Nginx配置,使nginx反向代理这些图片地址:
vim /opt/nginx/config/nginx.conf
修改成如下配置:
server {listen 80;server_name image.taotao.com;# 监听域名中带有group的,交给FastDFS模块处理location ~/group([0-9])/ {ngx_fastdfs_module;}# 将其它图片代理指向本地的/taotao/static目录location / {root /taotao/static/;}error_page 500 502 503 504 /50x.html;location = /50x.html {root html;}}
商品查询
实体类
SPU
@Table(name = "tb_spu")
public class Spu {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private Long brandId;private Long cid1;// 1级类目private Long cid2;// 2级类目private Long cid3;// 3级类目private String title;// 标题private String subTitle;// 子标题private Boolean saleable;// 是否上架private Boolean valid;// 是否有效,逻辑删除用private Date createTime;// 创建时间private Date lastUpdateTime;// 最后修改时间// 省略getter和setter
}
SPU详情
@Table(name="tb_spu_detail")
public class SpuDetail {@Idprivate Long spuId;// 对应的SPU的idprivate String description;// 商品描述private String specialSpec;// 商品特殊规格的名称及可选值模板private String genericSpec;// 商品的全局规格属性private String packingList;// 包装清单private String afterService;// 售后服务// 省略getter和setter
}
controller
先分析:
请求方式:GET
请求路径:/spu/page
请求参数:
- page:当前页
- rows:每页大小
- key:过滤条件
- saleable:上架或下架
返回结果:商品SPU的分页信息。
要注意,页面展示的是商品分类和品牌名称,而数据库中保存的是id,怎么办?
我们可以新建一个类,继承SPU,并且拓展cname和bname属性,写到taotao-item-interface`
public class SpuBo extends Spu {String cname;// 商品分类名称String bname;// 品牌名称// 略 。。 }
编写controller代码:
我们把与商品相关的一切业务接口都放到一起,起名为GoodsController,业务层也是这样
@RestController
public class GoodsController {@Autowiredprivate GoodsService goodsService;@GetMapping("spu/page")public ResponseEntity<PageResult<SpuBo>> querySpuByPage(@RequestParam(value = "page", defaultValue = "1") Integer page,@RequestParam(value = "rows", defaultValue = "5") Integer rows,@RequestParam(value = "saleable", required = false) Boolean saleable,@RequestParam(value = "key", required = false) String key) {PageResult<SpuBo> result = this.goodsService.querySpuPage(page, rows, saleable, key);if (result == null) {return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null);}return ResponseEntity.ok(result);}}
}
service
所有商品相关的业务(包括SPU和SKU)放到一个业务下:GoodsService。
@Service
public class GoodsService {@Autowiredprivate SpuMapper spuMapper;@Autowiredprivate CategoryService categoryService;@Autowiredprivate BrandMapper brandMapper;public PageResult<SpuBo> querySpuByPageAndSort(Integer page, Integer rows, Boolean saleable, String key) {// 1、查询SPU// 分页,最多允许查100条PageHelper.startPage(page, Math.min(rows, 200));// 创建查询条件Example example = new Example(Spu.class);Example.Criteria criteria = example.createCriteria();// 是否过滤上下架if (saleable != null) {criteria.orEqualTo("saleable", saleable);}// 是否模糊查询if (StringUtils.isNotBlank(key)) {criteria.andLike("title", "%" + key + "%");}Page<Spu> pageInfo = (Page<Spu>) this.spuMapper.selectByExample(example);List<SpuBo> list = pageInfo.getResult().stream().map(spu -> {// 把spu变为 spuBoSpuBo spuBo = new SpuBo();// 属性拷贝BeanUtils.copyProperties(spu, spuBo);// 2、查询spu的商品分类名称,要查三级分类List<String> names = this.categoryService.queryNameByIds(Arrays.asList(spu.getCid1(), spu.getCid2(), spu.getCid3()));// 将分类名称拼接后存入spuBo.setCname(StringUtils.join(names, "/"));// 3、查询spu的品牌名称Brand brand = this.brandMapper.selectByPrimaryKey(spu.getBrandId());spuBo.setBname(brand.getName());return spuBo;}).collect(Collectors.toList());return new PageResult<>(pageInfo.getTotal(), list);}
}
mapper
public interface SpuMapper extends Mapper<Spu> {}
Category中拓展查询名称的功能
页面需要商品的分类名称需要在这里查询,因此要额外提供查询分类名称的功能,
在CategoryService中添加功能:
public List<String> queryNamesByIds(List<Long> ids) {List<Category> list = this.categoryMapper.selectByIdList(ids);List<String> names = new ArrayList<>();for (Category category : list) {names.add(category.getName());}return names;// return list.stream().map(category -> category.getName()).collect(Collectors.toList());
}
mapper的selectByIDList方法是来自于通用mapper。不过需要我们在mapper上继承一个通用mapper接口:
public interface CategoryMapper extends Mapper<Category>, SelectByIdListMapper<Category, Long> { // ...coding
}
电商项目——商品规格管理相关推荐
- 电商项目——商品服务-API-属性分组——第十一章——上篇
电商项目--初识电商--第一章--上篇 电商项目--分布式基础概念和电商项目微服务架构图,划分图的详解--第二章--上篇 电商项目--电商项目的虚拟机环境搭建_VirtualBox,Vagrant-- ...
- element ui 电商后台商品属性管理页面
电商后台商品属性管理 最近在做电商后台,电商商品中有个SKU值,类似于 服装的 "尺码""颜色""款式"等. 界面如下: 所用到的知识点如下 ...
- 电商项目—商品的spu、sku概念及其之间的关系
电商项目-商品的spu.sku概念及其之间的关系 电商项目中涉及到商品时必然会遇到的几个概念,SPU.SKU.单品等.彻底搞懂和明白了这几个概念对我们设计商品表是十分必要的前提条件. SPU:标准化产 ...
- 微信小程序电商项目商品详情页开发实战之数据绑定与事件应用
各位CSDN的朋友,我们都知道,现在微信小程序电商平台特别火爆,所以我将以一个生鲜电商项目为例,为大家讲述微信小程序的实战化开发,价值几万元的成熟项目,你可千万不要错过哦. 大家直接通过视频链接直接看 ...
- 电商项目(一)---------Sku和Spu以及电商项目商品的设计思路
一,在电商项目里面为了准确的描述商品的区别,我们抽象出来两个概念,Spu和Sku这两个概念. Spu(标准产品单位 ):一组具有共同属性的商品集 Sku(库存量单位):SPU商品集因具体特性不同而细分 ...
- 微服务项目之电商--17.商品规格数据结构SPU和SKU
目录 1.商品规格数据结构 1.1.SPU和SKU 1.2.数据库设计分析 1.2.1.思考并发现问题 1.2.2.分析规格参数 1.2.3.SKU的特有属性 1.3.规格参数表 1.3.1.表结构 ...
- mysql中设计suk表_电商项目-商品表(spu)、规格表(sku)设计
之前在工作中,需要实现商品规格功能,做了很长一段时间,现在回过头来整理下设计思路. sku,spu概念: SPU = Standard Product Unit (标准化产品单元),SPU是商品信息聚 ...
- 电商项目--------------------商品(SKU)规格、价格功能
目录 1.功能介绍: 2.核心功能实现: 1.功能介绍: 当我们进入商品详情页面时 ①.商品的规格选项栏,规格选项栏目展示(可选择---部分展示,即已有规格选项) 默认展示基础价格商品,基础价格=商品 ...
- 【愚公系列】2022年10月 微信小程序-电商项目-商品详情页面规格选择功能实现
文章目录 前言 一.商品详情页面规格选择功能实现 二.效果 前言 vant-weapp的Popup 弹出层api Props 参数 说明 类型 默认值 show 是否显示弹出层 boolean fal ...
最新文章
- mysql的外键探讨
- 多语言互通:谷歌发布实体检索模型,涵盖超百种语言和千万个实体
- mac 上搭建gitlabel_在mac终端中使用git(适用于github、gitlabel)
- jvm垃圾回收器(《深入理解java虚拟机》)
- boost在DevC++中的安装过程
- kvm直通sata_基于KVM的SRIOV直通配置及性能测试
- POJ - 2676 Sudoku(dfs)
- 【Java】第一阶段练习题
- 服务器选购seo优化规则,需要做SEO的网站,购买服务器请注意六点
- 深入浅出 eBPF: (Linux/Kernel/XDP/BCC/BPFTrace/Cillium)
- 多层陶瓷电容器用处_具有综合优异电卡性能的无铅多层陶瓷电容器研究新进展...
- 北京54或国家80或CGCS2000转WGS84坐标系的程序实现方法
- 个人中心html界面设计,APP个人中心页面设计
- 如何重装win10应用商店?
- Matlab的卷积编码实现
- [DataAnalysis]基于统计假设检验的机器学习模型性能评估——泛化误差率的统计检验
- 拉昆塔温德姆酒店中国首店即将亮相山东潍坊;复星旅文旗下Club Med落子北美市场 | 全球旅报...
- recovery 工作流程
- z3求解器(SMT)解各类方程各种逻辑题非常简单直观
- 是兰大文科楼的辉煌灯火让我找到了自己
热门文章
- 51 单片机 程序 测量占空比 测量频率 频率计 占空比 proteus
- 计算机二级c语言模拟上机,计算机二级C语言上机模拟题
- hrbust 哈理工oj 1633 word!word!【欧拉路、欧拉回路的有向图判断】
- 【微信小程序】随机点名系统(点击开始滚动名字点击结束按钮结束滚动)
- 32位linux装64位rpm包,360浏览器提供rpm包(支持MIPS64)及32位deb包(兆芯)
- faster rcnn解读
- 京东618大促,全店快递如何批量打印
- 将查询好的快递单号以及物流导出EXCEL表格
- 线性表中的尾插法双链表的学习
- C++报错:引发了未经处理的异常:写入访问权限冲突, p 是 0xCCCCCCCC