電商設計手冊之基礎商品信息

前言

建議使用大屏設備(例如pad/pc),可以更好的瀏覽本篇文章

今天我們開始「商品系統」的篇章。本文分爲如下五大模塊:

  • 需求分析
  • 架構設計
  • Spu和Sku的故事
  • 數據模型設計
  • 接口設計

第一篇我們主要看看一個入門的電商平臺(B2C)如何去構建自己的基礎商品信息,其實這個事情很簡單,想想我們的現實生活,商家擺放商品到貨架,客戶從貨架挑選商品,客戶把挑選好的商品放入購物車(籃),最後客戶去收銀臺結賬

需求分析

對於一個電商平臺來講,我們怎麼理解上面的簡單示例呢?接着,我們來拆分上面這個簡單的事情:

商家擺放商品到貨架,客戶從貨架挑選商品,客戶把挑選好的商品放入購物車(籃),最後客戶去收銀臺結賬
  1. 商家是誰:電商平臺
  2. 擺放是什麼意思:上架
  3. 貨架在哪:前臺系統(web/app/...)
  4. 挑選:瀏覽前臺系統
  5. 放入:點擊前臺系統「加入購物車按鈕」
  6. ...(暫不多說了)

備註:本篇文章主要來看看1、2、3、4步該如何去設計。

通過上面的分析我們可以得出下面的信息:

  1. 我們需要一個「電商平臺」,電商平臺裏面需要有個商品後臺系統
  2. 我們上架什麼東西呢?商品!所以商品後臺系統需要具備創建發佈商品到前臺系統的功能。
  3. 我們需要一個前臺系統(比如網頁),前臺系統具備商品列表和商品詳情的頁面,可供用戶瀏覽
  4. 前臺系統的數據怎麼來?所以我們需要一個接口網關(對外統一提供服務能力,企業總線)和商品服務

整理之後得到如下的需求點:

需求點 功能點 項目命名 技術棧
商品後臺系統 1.創建商品 2.發佈商品到前臺系統 Temporal Backend PHP
前臺系統 1.商品列表 2.商品詳情 Skr Frontend Vue
接口網關 企業總線 Skr Gateway kong
商品服務 1.創建商品接口 2.商品狀態變更接口 2.商品列表接口 3. 商品詳情接口 Temporal Service Golang

架構設計

通過上面的需求分析,再加上之前的《電商設計手冊之用戶體系》中的用戶體系和《支付開發,不得不瞭解的國內、國際第三方支付流程》中的支付服務,我們規劃出以下的架構圖。

<p align="center">

<a href="http://cdn.tigerb.cn/skr-product-service.jpg" data-lightbox="roadtrip">
    <img src="http://cdn.tigerb.cn/skr-product-service.jpg">
</a>

</p>

Spu和Sku的故事

對我們程序猿來講「商品系統」剛開始的樣子就是如下三點:

  1. 創建商品功能:首先我們會有一張商品表,每創建一個商品我們會的到一個goods_id,如果商品存在父子的關係,加一個parent_id的字段就搞定了。
  2. 商品列表接口:商品表分頁查詢商品。
  3. 商品詳情接口:商品表按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,爲什麼呢?顧名思義,冗餘用的,有了這張表,我們可以很高效的得到:

  1. spu下 有哪些sku
  2. spu下 有那些銷售屬性
  3. spu下 每個銷售屬性對應的銷售屬性值(一對多)
  4. 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, 市場價",
            }
        ]
    }
}

結語

最後,如果有寫的不對或者不完善的地方,希望大家多多評論,互相學習互相進步~

項目地址: https://github.com/skr-shop/m...

下篇預告

下篇文章我們主要專注到基礎商品信息的前端交互設計,比如Spu詳情頁面多銷售屬性的選擇如何聯動等,盡情期待。

圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章