前言
建議使用大屏裝置(例如pad/pc),可以更好的瀏覽本篇文章
今天我們開始「商品系統」的篇章。本文分為如下五大模組:
- 需求分析
- 架構設計
- 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下 有哪些sku
- spu下 有那些銷售屬性
- spu下 每個銷售屬性對應的銷售屬性值(一對多)
- spu下 每個銷售屬性值對應的sku(一對多)
具體表結構如下所示:
-- 品牌表 product_brands
CREATE 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_category
CREATE 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_attr
CREATE 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_value
CREATE 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_stock
CREATE 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, 市場價",
}
]
}
}
複製程式碼
結語
最後,如果有寫的不對或者不完善的地方,希望大家多多評論,互相學習互相進步~
專案地址: github.com/skr-shop/ma…
下篇預告
下篇文章我們主要專注到基礎商品資訊的前端互動設計,比如Spu詳情頁面多銷售屬性的選擇如何聯動等,盡情期待。