本日我们开始「商品系统」的篇章。本文分为如下五大模块:
需求剖析架构设计Spu和Sku的故事数据模型设计接口设计第一篇我们紧张看看一个入门的电商平台(B2C)如何去构建自己的根本商品信息,实在这个事情很大略,想想我们的现实生活,商家摆放商品到货架,客户从货架挑选商品,客户把挑选好的商品放入购物车(篮),末了客户去收银台结账。
对付一个电商平台来讲,我们怎么理解上面的大略示例呢?接着,我们来拆分上面这个大略的事情:

商家摆放商品到货架,客户从货架挑选商品,客户把挑选好的商品放入购物车(篮),末了客户去收银台结账
商家是谁:电商平台摆放是什么意思:上架货架在哪:前台系统(web/app/...)挑选:浏览前台系统放入:点击前台系统「加入购物车按钮」...(暂不多说了)备注:本篇文章紧张来看看1、2、3、4步该如何去设计。
通过上面的剖析我们可以得出下面的信息:
我们须要一个「电商平台」,电商平台里面须要有个商品后台系统。我们上架什么东西呢?商品!以是商品后台系统须要具备创建和发布商品到前台系统的功能。我们须要一个前台系统(比如网页),前台系统具备商品列表和商品详情的页面,可供用户浏览。前台系统的数据怎么来?以是我们须要一个接口网关(对外统一供应做事能力,企业总线)和商品做事
整理之后得到如下的需求点:
需求点
功能点
项目命名
技能栈
商品后台系统
1.创建商品 2.发布商品到前台系统
Temporal Backend
PHP
前台系统
1.商品列表 2.商品详情
Skr Frontend
Vue
接口网关
企业总线
Skr Gateway
kong
商品做事
1.创建商品接口 2.商品状态变更接口 2.商品列表接口 3. 商品详情接口
Temporal Service
Golang
架构设计通过上面的需求剖析,再加上之前的《电商设计手册之用户体系》中的用户体系和《支付开拓,不得不理解的海内、国际第三方支付流程》中的支付做事,我们方案出以下的架构图。
Spu和Sku的故事
对我们程序猿来讲「商品系统」刚开始的样子便是如下三点:
创建商品功能:首先我们会有一张商品表,每创建一个商品我们会的到一个goods_id,如果商品存在父子的关系,加一个parent_id的字段就搞定了。商品列表接口:商品表分页查询商品。商品详情接口:商品表按goods_id索引查询商品信息。很大略是吧,基本一张表就搞定了,看起来也是没什么问题的。但是呢,程序设计的奥妙之处就在于抽象能力,电商行业把goods_id进行了进一步的抽象,产生了Spu和Sku观点,在理解Spu和Sku定义之前,我们还得理解下发卖属性的含义,举个例子便于理解:
想想我们的现实生活,如果我们去批发市场上了一批AJ1球鞋,批发商会给我们不同配色、大小的AJ1球鞋。我们在店里发卖这些商品时都会讯问客户:“您是须要什么颜色和大小的AJ1球鞋呢?”。这里的颜色和大小便是所谓的发卖属性,由于不同颜色和大小的AJ1球鞋可能价格不同、库存数量不同,现实生活中是不是如此,不同颜色或大小的AJ1都有差别巨大的价格。
接着,我们来看看Spu和Sku定义:
名称
观点
阐明
Spu
standard product unit 标准产品单位
goods_id剥离发卖属性的部分,例如:小米8。商品列表我们展示Spu列表。
Sku
stock keeping unit 库存量单位
便是你想买的那个商品真正的编号,这个编号对应的库存便是你想买的那个商品的库存量。Spu+一或多个发卖属性对应一个Sku,例如:小米8黑128G,个中黑和128G便是发卖属性,小米8便是一个Spu。
搞清楚了么?
数据模型设计以是末了大略的商品表就拆成了spu表和sku表,接着我们还抽象出来了可复用的发卖属性表和发卖属性值表。除此之外 我们该当还有品牌表、种别表、大略的sku库存表(目前大略设计此表,后期详细业务重构此表)。接着我们列下这些表的明细:
表名称
表名
品牌表
product_brands
种别表
product_category
spu表
product_spu
sku表
product_sku
发卖属性表
product_attr
发卖属性值
product_attr_value
sku库存表
product_sku_stock
除了上面的表之外,我又加了另一张表 关联关系冗余表 product_spu_sku_attr_map,为什么呢?顾名思义,冗余用的,有了这张表,我们可以很高效的的到:
spu下 有哪些skuspu下 有那些发卖属性spu下 每个发卖属性对应的发卖属性值(一对多)spu下 每个发卖属性值对应的sku(一对多)详细表构造如下所示:
-- 品牌表 product_brandsCREATE TABLE `product_brands` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '品牌ID', `name` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '品牌名称', `desc` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '品牌描述', `logo_url` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '品牌logo图片', `create_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建韶光', `create_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人staff_id', `update_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新韶光', `update_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修君子staff_id', `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='品牌表';-- 种别表 product_categoryCREATE TABLE `product_category` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '分类ID', `pid` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '父ID', `name` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '分类名称', `desc` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '分类描述', `pic_url` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '分类图片', `path` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '分类地址{pid}-{child_id}-...', `create_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建韶光', `create_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人staff_id', `update_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新韶光', `update_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修君子staff_id', `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='种别表';-- spu表 product_spu-- spu: standard product unit 标准产品单位CREATE TABLE `product_spu` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'SPU ID', `brand_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '品牌ID', `category_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '分类ID', `name` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT 'spu名称', `desc` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT 'spu描述', `selling_point` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '卖点', `unit` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT 'spu单位', `banner_url` text COMMENT 'banner图片 多个图片逗号分隔', `main_url` text COMMENT '商品先容主图 多个图片逗号分隔', `price_fee` int unsigned NOT NULL DEFAULT 0 COMMENT '售价,整数办法保存', `price_scale` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '售价,金额对应的小数位数', `market_price_fee` int unsigned NOT NULL DEFAULT 0 COMMENT '市场价,整数办法保存', `market_price_scale` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '市场价,金额对应的小数位数', `create_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建韶光', `create_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人staff_id', `update_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新韶光', `update_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修君子staff_id', `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT AUTO_INCREMENT=666666 CHARSET=utf8mb4 COMMENT='spu表';-- sku表 product_sku-- sku: stock keeping unit 库存量单位CREATE TABLE `product_sku` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT 'SKU ID', `spu_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'SPU ID', `attrs` text COMMENT '发卖属性值{attr_value_id}-{attr_value_id} 多个发卖属性值逗号分隔', `banner_url` text COMMENT 'banner图片 多个图片逗号分隔', `main_url` text COMMENT '商品先容主图 多个图片逗号分隔', `price_fee` int unsigned NOT NULL DEFAULT 0 COMMENT '售价,整数办法保存', `price_scale` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '售价,金额对应的小数位数', `market_price_fee` int unsigned NOT NULL DEFAULT 0 COMMENT '市场价,整数办法保存', `market_price_scale` tinyint unsigned NOT NULL DEFAULT 0 COMMENT '市场价,金额对应的小数位数', `create_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建韶光', `create_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人staff_id', `update_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新韶光', `update_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修君子staff_id', `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT AUTO_INCREMENT=666666 CHARSET=utf8mb4 COMMENT='sku表';-- 发卖属性表 product_attrCREATE TABLE `product_attr` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '发卖属性ID', `name` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '发卖属性名称', `desc` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '发卖属性描述', `create_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建韶光', `create_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人staff_id', `update_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新韶光', `update_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修君子staff_id', `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发卖属性表';-- 发卖属性值 product_attr_valueCREATE TABLE `product_attr_value` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '发卖属性值ID', `attr_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '发卖属性ID', `value` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '发卖属性值', `desc` varchar(255) unsigned NOT NULL DEFAULT '' COMMENT '发卖属性值描述', `create_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建韶光', `create_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人staff_id', `update_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新韶光', `update_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修君子staff_id', `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='发卖属性值';-- 关联关系冗余表 product_spu_sku_attr_map-- 1. spu下 有哪些sku-- 2. spu下 有那些发卖属性 -- 3. spu下 每个发卖属性对应的发卖属性值(一对多) -- 4. spu下 每个发卖属性值对应的sku(一对多)CREATE TABLE `product_spu_sku_attr_map` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', `spu_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'SPU ID', `sku_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'SKU ID', `attr_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '发卖属性ID', `attr_name` varchar(255) NOT NULL DEFAULT '0' COMMENT '发卖属性名称', `attr_value_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '发卖属性值ID', `attr_value_name` varchar(255) NOT NULL DEFAULT '0' COMMENT '发卖属性值', `create_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建韶光', `create_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人staff_id', `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='关联关系冗余表';-- sku库存表 product_sku_stockCREATE TABLE `product_sku_stock` ( `id` int(11) unsigned NOT NULL AUTO_INCREMENT COMMENT '自增ID', `sku_id` int(11) unsigned NOT NULL DEFAULT '0' COMMENT 'SKU ID', `quantity` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '库存', `create_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建韶光', `create_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '创建人staff_id', `update_at` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '更新韶光', `update_by` int(11) unsigned NOT NULL DEFAULT '0' COMMENT '修君子staff_id', `status` tinyint(1) unsigned NOT NULL DEFAULT '0' COMMENT '状态 1:enable, 0:disable, -1:deleted', PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='sku库存表';
接口设计
关于接口设计目前很大略,无非列表和详情。但是这里我做了一个很好的设计动静分离,例如库存的动态的数据,单独供应接口,其他列表和详情数据完备静态化,把流量打到CDN去,这里又会说到我们下步操持的根本做事体系里的「静态资源做事」,这个做事的紧张功能便是把我们的接口数据静态化。详细的V1.0版的接口设计如下:
1、spu详情 GET {version}/product/spu/{spu_id}
要求参数:
字段
类型
是否必传
描述
spu_id
number
yes
spu ID
相应内容:
{ "code": "200", "msg": "OK", "result": { "brand_info": { "id": "number, 品牌ID", "name": "string, 品牌名称", "desc": "string, 品牌描述", "logo_url": "string, 品牌logo图片", }, "category_info": { "id": "number, 分类ID", "name": "string, 品牌名称", "desc": "string, 品牌描述", "pic_url": "string, 分类图片", "path": "string, 分类地址{pid}-{child_id}-...", }, "spu_info": { "id": "number, spu id", "name": "string, spu名称", "desc": "string, spu描述", "selling_point": "string, 卖点", "unit": "string, spu单位", "banner_url": [ "string, banner 图片url", "string, banner 图片url", ], "main_url": [ "string, 商品先容主图 图片url", "string, 商品先容主图 图片url", ], "price": "string, 售价", "market_price": "string, 市场价", "attrs": [ // 有那些发卖属性 { "id": "发卖属性ID", "name": "string, 发卖属性名称", "desc": "string, 发卖属性描述", "values": [ // 每个发卖属性对应的发卖属性值(一对多) { "id": "发卖属性值ID", "name": "string, 发卖属性值", "desc": "string, 发卖属性值描述", // 每个发卖属性值对应的sku(一对多) // 页面初始化时,按钮不可点击逻辑判断: 如果该发卖属性值下所有sku没有库存,则该发卖属性按钮不可点击 // 选择发卖属性值时,按钮不可点击逻辑判断:发卖属性构成双向链表,每个发卖属性又是一个单向链表存改发卖属性对应的所有发卖属性值。每当选择一个发卖属性值时先前和后一个发卖属性遍历,执发卖属性值下所有sku售罄的按钮不可点击,且当前发卖属性值map记录key为当前点击的发卖属性值ID,值统一标示一下就行,目的记录是由于选择了哪个发卖属性值使得当前的发卖属性值为售罄状态 // 取消选择发卖属性值时,按钮不可点击逻辑规复判断:数据构造同上,遍历,记录的map删除key为当前取消选中的发卖属性值,并判断是否还有别的key使得该发卖属性值为售罄状态,如果没有则规复未售罄状态 "skus": [ "number, sku id", "number, sku id", ], } ], } ], "skus": [ // 有哪些sku "number, sku id", "number, sku id", ], "skus_map": { "{attr_value_id}-{attr_value_id}-...": "number, sku id", "{attr_value_id}-{attr_value_id}-...": "number, sku id", "{attr_value_id}-{attr_value_id}-...": "number, sku id", "{attr_value_id}-{attr_value_id}-...": "number, sku id", "{attr_value_id}-{attr_value_id}-...": "number, sku id", "{attr_value_id}-{attr_value_id}-...": "number, sku id", } } }}
2、获取spu下所有skus库存 GET {version}/stock/spu/{spu_id}
要求参数:
字段
类型
是否必传
描述
spu_id
number
yes
spu ID
相应内容:
{ "code": "200", "msg": "OK", "result": { "skus_stock": { "int, sku id": { "quantity": "int, 剩余库存数量" } } } }}
3、sku详情 GET {version}/product/sku/{sku_id}
要求参数:
字段
类型
是否必传
描述
sku
number
yes
sku ID
相应内容:
{ "code": "200", "msg": "OK", "result": { "id": "number, sku id", "name": "string, sku名称", "desc": "string, sku描述", "unit": "string, sku单位", "banner_url": [ "string, banner 图片url", "string, banner 图片url", ], "main_url": [ "string, 商品先容主图 图片url", "string, 商品先容主图 图片url", ], "price": "string, 售价", "market_price": "string, 市场价", }}
4、spu列表 GET {version}/product/spu/list
要求参数:
字段
类型
是否必传
描述
-
-
-
-
相应内容:
{ "code": "200", "msg": "OK", "result": { "list": [ { "id": "number, spu id", "name": "string, spu名称", "desc": "string, spu描述", "unit": "string, spu单位", "banner_url": [ "string, banner 图片url", "string, banner 图片url", ], "price": "string, 售价", "market_price": "string, 市场价", } ] }}
结语
末了,如果有写的不对或者不完善的地方,希望大家多多评论,相互学习相互进步~
项目地址: