掌握SKU和SPU關系及表設計(三)


一、掌握SKU和SPU關系

電商系統中涉及到商品時必然會遇到的幾個概念,SPU、SKU、單品等。徹底搞懂和明白了這幾個概念對設計商品表是十分必要的前提條件。

1.1、SPU:標准化產品單元

SPU = Standard Product Unit (標准化產品單元),SPU是商品信息聚合的最小單位,是一組可復用、易檢索的標准化信息的集合,該集合描述了一個產品的特性。

1.2、SKU:庫存量單位

SKU=stock keeping unit(庫存量單位) SKU即庫存進出計量的單位(買家購買、商家進貨、供應商備貨、工廠生產都是依據SKU進行的)。SKU是物理上不可分割的最小存貨單元。也就是說一款商品,可以根據SKU來確定具體的貨物存量。如一件M碼(四個尺碼:S碼、M碼、L碼、X碼)的粉色(三種顏色:粉色、黃色、黑色)Zara女士風衣,其中M碼、粉色就是一組SKU的組合。SKU在生成時, 會根據屬性生成相應的笛卡爾積,根據一組SKU可以確定商品的庫存情況,那么上面的Zara女士風衣一共有4 * 3 = 12個SKU組合。M碼+粉色這兩個屬性組合被稱為一組SKU、因為SKU是物理上不可分割的最小存貨單元,單憑尺寸或者顏色是沒有辦法確認這款商品的庫存情況。同理商家進貨補貨也是通過SKU來完成的,試問淘寶店家跟供貨商說我要100件紅色女士風衣?供應商知道該怎么給他備貨嗎?顯然是不知道的。因為還欠缺了另外的一個銷售屬性【尺碼】。

1.3、spu和sku都是屬性值的集合

SPU 屬性(不會影響到庫存和價格的屬性, 又叫關鍵屬性)

Oppo R17這是商品的SPU,但Oppo R17只是一個名詞,單純的理解這個名詞是沒有意義的。

Oppo R17是這個商品的SPU,這里的SPU是一組商品的屬性組合。如下所示

【硬件參數】:

CPU 型號:高通驍龍™ 670

CPU 頻率:2.0GHz

核心數:八核

處理器位數:64 位

GPU 型號:Adreno™ 615

電池容量:3500mAh(典型值)*

【尺寸】:

長:約 157.5mm

寬:約 74.9mm

厚:約 7.5mm

重:約 182g

以及包括【攝像頭】、【顯示屏】、【操作系統】等等這些屬性構成了一個SPU、這個SPU屬性組合的名稱叫做Oppo R17。

spu : 包含在每一部 oppo r17 的屬性集合, 與商品是一對一的關系(產地:中國, 毛重:182g...)

SKU 屬性(會影響到庫存和價格的屬性, 又叫銷售屬性)

sku : 影響價格和庫存的 屬性集合, 與商品是多對一的關系,即一個商品有多個SKU。

如流光藍(三種顏色:流光藍、霓光紫、霓光漸變色)+8G+128G(兩種配置:8G+128G、6G+128G)。

即Oppo R17有一個SPU、6種SKU。

 

單品 : 國人對於SKU的另外一種叫法。

1.4、SKU和商品之間的關系

1)SKU(或稱商品SKU)指的是商品子實體。

 

2)商品SPU和商品SKU是包含關系,一個商品SPU包含若干個商品SKU子實體,商品SKU從屬於商品SPU。

 

3)SKU不是編碼,每個SKU包含一個唯一編碼,即SKU Code,用於管理。

上面是從網上搞的,這么看可能還不是太明白,簡單的程序思想說,如果所以手機信息存在一張表里,但對於同一家手機來說,廠家,分類及品牌都是相同的信息,假設這此相同的信息每條占10M內存,如果數據多了,這就形成了數據冗余,冗余數據占了不必要的內存那就要優化了,因為對電商來說內存是很寶貴的,那這時SPU概念就出來了,SPU概念就是把這些共性的數據抽取來,這一抽后,本來占30M的內存我只用存一條,那內存就節約了2/3,對於不內的信息比喻內存、顏色、價格的我單獨再存一張表中這樣性能就好很多,不同的屬性就是SKU。這大白話一說大概就明白了。

二、數據庫表的設計

前面說了SPU和SKU的關系和概念,下面就來學着電商網站設計一下SPU及SKU表,表結構如下

 

 

SPU:

-- ----------------------------
DROP TABLE IF EXISTS `spu`;
CREATE TABLE `spu` (
  `id` varchar(60) NOT NULL COMMENT '主鍵',
  `name` varchar(100) DEFAULT NULL COMMENT 'SPU名',
  `intro` varchar(200) DEFAULT NULL COMMENT '簡介',
  `brand_id` int(11) DEFAULT NULL COMMENT '品牌ID',
  `category_one_id` int(20) DEFAULT NULL COMMENT '一級分類',
  `category_two_id` int(10) DEFAULT NULL COMMENT '二級分類',
  `category_three_id` int(10) DEFAULT NULL COMMENT '三級分類',
  `images` varchar(1000) DEFAULT NULL COMMENT '圖片列表',
  `after_sales_service` varchar(50) DEFAULT NULL COMMENT '售后服務',
  `content` longtext COMMENT '介紹',
  `attribute_list` varchar(3000) DEFAULT NULL COMMENT '規格列表',
  `is_marketable` int(1) DEFAULT '0' COMMENT '是否上架,0已下架,1已上架',
  `is_delete` int(1) DEFAULT '0' COMMENT '是否刪除,0:未刪除,1:已刪除',
  `status` int(1) DEFAULT '0' COMMENT '審核狀態,0:未審核,1:已審核,2:審核不通過',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

SKU;

DROP TABLE IF EXISTS `sku`;
CREATE TABLE `sku` (
  `id` varchar(60) NOT NULL COMMENT '商品id',
  `name` varchar(200) NOT NULL COMMENT 'SKU名稱',
  `price` int(20) NOT NULL DEFAULT '1' COMMENT '價格(分)',
  `num` int(10) DEFAULT '100' COMMENT '庫存數量',
  `image` varchar(200) DEFAULT NULL COMMENT '商品圖片',
  `images` varchar(2000) DEFAULT NULL COMMENT '商品圖片列表',
  `create_time` datetime DEFAULT NULL COMMENT '創建時間',
  `update_time` datetime DEFAULT NULL COMMENT '更新時間',
  `spu_id` varchar(60) DEFAULT NULL COMMENT 'SPUID',
  `category_id` int(10) DEFAULT NULL COMMENT '類目ID',
  `category_name` varchar(200) DEFAULT NULL COMMENT '類目名稱',
  `brand_id` int(11) DEFAULT NULL COMMENT '品牌id',
  `brand_name` varchar(100) DEFAULT NULL COMMENT '品牌名稱',
  `sku_attribute` varchar(200) DEFAULT NULL COMMENT '規格',
  `status` int(1) DEFAULT '1' COMMENT '商品狀態 1-正常,2-下架,3-刪除',
  PRIMARY KEY (`id`),
  KEY `cid` (`category_id`),
  KEY `status` (`status`),
  KEY `updated` (`update_time`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='商品表';

2.1、商品發布流程分析

大家在購物時會發現,當選擇商品的類別時,下面的商品名稱及商品的圖片和商品店家信息什么都是動態變化的。這里面就設計到設計了,一般在設計時在發布商品前,需要先選擇發布商品所屬分類,分類嚴格定義為3級分類。

分類表設計如下:

DROP TABLE IF EXISTS `category`;
CREATE TABLE `category` (
  `id` int(20) NOT NULL AUTO_INCREMENT COMMENT '分類ID',
  `name` varchar(50) DEFAULT NULL COMMENT '分類名稱',
  `sort` int(11) DEFAULT NULL COMMENT '排序',
  `parent_id` int(20) DEFAULT NULL COMMENT '上級ID',
  PRIMARY KEY (`id`),
  KEY `parent_id` (`parent_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11182 DEFAULT CHARSET=utf8 COMMENT='商品類目';
分類選擇完成后,需要加載品牌,品牌加載並非一次性加載完成,而是根據選擇的分類進行加載。分類品牌關系表:
DROP TABLE IF EXISTS `category_brand`;
CREATE TABLE `category_brand` (
  `category_id` int(11) NOT NULL COMMENT '分類ID',
  `brand_id` int(11) NOT NULL COMMENT '品牌ID',
  PRIMARY KEY (`brand_id`,`category_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
品牌表:
DROP TABLE IF EXISTS `brand`;
CREATE TABLE `brand` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '品牌id',
  `name` varchar(100) NOT NULL COMMENT '品牌名稱',
  `image` varchar(1000) DEFAULT '' COMMENT '品牌圖片地址',
  `initial` varchar(1) DEFAULT '' COMMENT '品牌的首字母',
  `sort` int(11) DEFAULT NULL COMMENT '排序',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8 COMMENT='品牌表';
當選擇分類后,會加載分類對應的屬性。
分類屬性表:
DROP TABLE IF EXISTS `category_attr`;
CREATE TABLE `category_attr` (
  `category_id` int(11) NOT NULL,
  `attr_id` int(11) NOT NULL COMMENT '屬性分類表',
  PRIMARY KEY (`category_id`,`attr_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
屬性表:
DROP TABLE IF EXISTS `sku_attribute`;
CREATE TABLE `sku_attribute` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'ID',
  `name` varchar(50) DEFAULT NULL COMMENT '屬性名稱',
  `options` varchar(2000) DEFAULT NULL COMMENT '屬性選項',
  `sort` int(11) DEFAULT NULL COMMENT '排序',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=13 DEFAULT CHARSET=utf8;

上面表設計好了,下面就將它的實體類全部寫好

 

 

@Data
@NoArgsConstructor
@AllArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "brand")
public class Brand implements Serializable {
    //    品牌ID
    //    MyBatisPlus主鍵策略注解
    @TableId(type= IdType.AUTO)
    private Integer id;
    //    品牌名字
    private String name;
    //    品牌圖片
    private String image;
    //    品牌首字母
    private String initial;
    //    品牌排序
    private Integer sort;

    //分類
    @TableField(exist = false)
    private List<Category> categories;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "category")
public class Category implements Serializable {

    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private Integer sort;
    private Integer parentId;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "category_attr")
public class CategoryAttr {

    @TableField
    private Integer categoryId;

    @TableField
    private Integer attrId;

}
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "category_brand")
public class CategoryBrand {

    @TableField
    private Integer categoryId;

    @TableField
    private Integer brandId;
}
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    // Spu
    private Spu spu;
    // Sku
    private List<Sku> skus;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "sku")
public class Sku {

    @TableId(type = IdType.ASSIGN_ID)
    private String id;
    private String name;
    private Integer price;
    private Integer num;
    private String image;
    private String images;
    private Date createTime;
    private Date updateTime;
    private String spuId;
    private Integer categoryId;
    private String categoryName;
    private Integer brandId;
    private String brandName;
    private String skuAttribute;
    private Integer status;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "sku_attribute")
public class SkuAttribute implements Serializable {

    @TableId(type = IdType.AUTO)
    private Integer id;
    private String name;
    private String options;
    private Integer sort;

    //對應分類
    @TableField(exist = false)
    private List<Category> categories;

}
@Data
@AllArgsConstructor
@NoArgsConstructor
//MyBatisPlus表映射注解
@TableName(value = "spu")
public class Spu {

    @TableId(type = IdType.ASSIGN_ID)
    private String id;
    private String name;
    private String intro;
    private Integer brandId;
    private Integer categoryOneId;
    private Integer categoryTwoId;
    private Integer categoryThreeId;
    private String images;
    private String afterSalesService;
    private String content;
    private String attributeList;
    private Integer isMarketable;
    private Integer isDelete;
    private Integer status;
}

接下來寫分類表的Mapper

 

接着寫業務層代碼,這塊代碼全在spring-cloud-goods-service項目中

public interface CategoryService extends IService<Category> {

    /***
     * 根據分類父ID查詢所有子類
     */
    List<Category> findByParentId(Integer pid);
}
@Service
public class CategoryServiceImpl extends ServiceImpl<CategoryMapper,Category> implements CategoryService {

    @Resource
    private CategoryMapper categoryMapper;

    /***
     * 根據分類父ID查詢所有子類
     */
    @Override
    public List<Category> findByParentId(Integer pid) {
        //條件封裝對象
        QueryWrapper<Category> queryWrapper = new QueryWrapper<Category>();
        queryWrapper.eq("parent_id",pid);
        return categoryMapper.selectList(queryWrapper);
    }
}

最后寫上控制層代碼

@RestController
@RequestMapping(value = "/category")
@CrossOrigin
public class CategoryController {

    @Autowired
    private CategoryService categoryService;

    /****
     * 根據分類父ID查詢子分類
     */
    @GetMapping(value = "/parent/{id}")
    public RespResult<List<Category>> findByParentId(@PathVariable("id")Integer id){
        return RespResult.ok(categoryService.findByParentId(id));
    }

}
品牌需要根據分類進行加載,當用戶選擇第3級分類的時候,加載品牌,品牌數據需要經過category_brand 表關聯查詢。所以接下來寫品牌表的邏輯。實現步驟是這樣的先從category_brand中指定分類對應的品牌ID集合然后從brand查出品牌集合
public interface BrandMapper extends BaseMapper<Brand> {
    /****
     * 1、根據分類ID查詢品牌ID集合
     */
    @Select("SELECT brand_id FROM category_brand WHERE category_id=#{id}")
    List<Integer> queryBrandIds(Integer id);
}
public interface BrandService extends IService<Brand> {



    /***
     * 根據分類ID查詢品牌集合
     */
    List<Brand> queryByCategoryId(Integer id);
}
@Service
public class BrandServiceImpl extends ServiceImpl<BrandMapper,Brand> implements BrandService {


    @Resource
    private BrandMapper brandMapper;


    /****
     * 根據分類ID查詢品牌集合
     * @param id:分類ID
     * @return
     */
    @Override
    public List<Brand> queryByCategoryId(Integer id) {
        //根據分類ID查詢品牌ID集合
        List<Integer> brandIds = brandMapper.queryBrandIds(id);
        //根據品牌ID集合查詢品牌集合
        if(brandIds!=null && brandIds.size()>0){
            return brandMapper.selectList(new QueryWrapper<Brand>().in("id",brandIds));
        }
        return null;
    }
}
@RestController
@RequestMapping(value = "/brand")
@CrossOrigin
public class BrandController {

    @Autowired
    private BrandService brandService;



    /****
     * 根據分類ID查詢品牌集合
     */
    @GetMapping(value = "/category/{pid}")
    public RespResult<List<Brand>>  categoryBrands(@PathVariable(value = "pid")Integer pid){
        List<Brand> brands = brandService.queryByCategoryId(pid);
        return RespResult.ok(brands);
    }
}

分類和品牌搞定的,接下來按電商頁面的風格就是查找屬性了,因為屬性也需要根據分類查詢,所以可以按照如下思路實現:

1、先從category_attr根據分類ID查詢出當前分類擁有的屬性ID集合
2、從sku_attribute中查詢屬性集合
有了思路接下來就是寫代碼了
public interface SkuAttributeMapper extends BaseMapper<SkuAttribute> {

    /****
     * 1、根據分類ID查詢屬性ID集合
     * 2、根據屬性ID集合查詢屬性集合
     */
    @Select("select * from sku_attribute where id IN(SELECT attr_id FROM category_attr WHERE category_id=#{id})")
    List<SkuAttribute> queryByCategoryId(Integer id);

}
public interface SkuAttributeService extends IService<SkuAttribute> {


    /***
     * 根據分類ID查詢屬性加集合
     */
    List<SkuAttribute> queryList(Integer id);
}
@Service
public class SkuAttributeServiceImpl extends ServiceImpl<SkuAttributeMapper,SkuAttribute> implements SkuAttributeService {

    @Autowired
    private SkuAttributeMapper skuAttributeMapper;

    /*****
     * 根據分類ID查詢屬性集合
     * @param id
     * @return
     */
    @Override
    public List<SkuAttribute> queryList(Integer id) {
        return skuAttributeMapper.queryByCategoryId(id);
    }
}
@RestController
@RequestMapping(value = "/skuAttribute")
@CrossOrigin
public class SkuAttributeController {

    @Autowired
    private SkuAttributeService skuAttributeService;

    /*****
     * 根據分類ID查詢屬性集合
     */
    @GetMapping(value = "/category/{id}")
    public RespResult<List<SkuAttributeController>> categorySkuAttributeList(@PathVariable(value = "id")Integer id){
        List<SkuAttribute> skuAttributes = skuAttributeService.queryList(id);
        return RespResult.ok(skuAttributes);
    }
}

上面的步驟搞完后,接下來就是產品的發布保存了,現在想要了解的是在產品保存時,哪些東西需要保存,首先,商品發布保存時一定存在Sku和Spu,因此后端能有一個對象同時能接到Spu和多個Sku,方法有很多種,可以直接在Spu中寫一個 List<Sku> ,但這種方法不推薦,按照對象設計原則,對一個對象進行擴展時,盡量避免對原始對象造成改變,因此可以使用復合類,可以創建一個 Prodcut 類,該類中有Spu也有 List<Sku> ,代碼如下:

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Product {
    // Spu
    private Spu spu;
    // Sku
    private List<Sku> skus;
}

 

public interface SkuMapper extends BaseMapper<Sku> {
}
public interface SpuMapper extends BaseMapper<Spu> {
}
public interface SpuService extends IService<Spu> {

    /****
     * 產品保存
     */
    void saveProduct(Product product);
}

 

 
@Service
public class SpuServiceImpl extends ServiceImpl<SpuMapper, Spu> implements SpuService {

    @Resource
    private SpuMapper spuMapper;

    @Resource
    private SkuMapper skuMapper;

    @Resource
    private CategoryMapper categoryMapper;

    @Resource
    private BrandMapper brandMapper;
    @Override
    public void saveProduct(Product product) {
//1.保存Spu
        Spu spu = product.getSpu();


            spu.setIsMarketable(1); //已上架
            spu.setIsDelete(0); //未刪除
            spu.setStatus(1);   //審核已通過
            spuMapper.insert(spu);


        //2.保存List<Sku>
        Date date = new Date();
        //查詢分類
        Category category = categoryMapper.selectById(spu.getCategoryThreeId());
        //查詢品牌
        Brand brand = brandMapper.selectById(spu.getBrandId());
        for (Sku sku : product.getSkus()) {
            //SKU名稱
            String name = spu.getName();
            Map<String,String> skuattrMap = JSON.parseObject(sku.getSkuAttribute(),Map.class);
            for (Map.Entry<String, String> entry : skuattrMap.entrySet()) {
                name+="  "+entry.getValue();
            }
            sku.setName(name);
            //創建時間
            sku.setCreateTime(date);
            //修改時間
            sku.setUpdateTime(date);
            //分類ID
            sku.setCategoryId(spu.getCategoryThreeId());
            //分類名字,這個是需要查詢得到的
            sku.setBrandName(brand.getName());
            //品牌ID
            sku.setBrandId(spu.getBrandId());
            //品牌名字
            sku.setCategoryName(category.getName());
            //spuid
            sku.setSpuId(spu.getId());
            //狀態 商品狀態 1-正常,2-下架,3-刪除
            sku.setStatus(1);

            //添加
            skuMapper.insert(sku);
        }
    }
}
@RestController
@RequestMapping(value = "/spu")
@CrossOrigin
public class SpuController {

    @Autowired
    private SpuService spuService;

    /*****
     * 產品保存
     */
    @PostMapping(value = "/save")
    public RespResult save(@RequestBody Product product){
        spuService.saveProduct(product);
        return RespResult.ok();
    }
}

上面是產品的保存功能,但是不管是什么平台,后台都是支持內容的修改的,電商產品平台也一樣,也具有修改功能修改代碼其實也就是在上面代碼改點東西而已,為減少接口我偷下懶,用一個概念偷換,因為增加的數據Id一定是空的,但是修改的數據他的id是有值的,就用這個概念來共用一個接口來實現修改功能;修改下SpuServiceImpl類

@Service
public class SpuServiceImpl extends ServiceImpl<SpuMapper, Spu> implements SpuService {

    @Resource
    private SpuMapper spuMapper;

    @Resource
    private SkuMapper skuMapper;

    @Resource
    private CategoryMapper categoryMapper;

    @Resource
    private BrandMapper brandMapper;
    @Override
    public void saveProduct(Product product) {
        //1.保存Spu
        Spu spu = product.getSpu();

        if(StringUtils.isEmpty(spu.getId())){
            spu.setIsMarketable(1); //已上架
            spu.setIsDelete(0); //未刪除
            spu.setStatus(1);   //審核已通過
            spuMapper.insert(spu);
        }else{
            //修改
            spuMapper.updateById(spu);
            //刪除Sku集合
            skuMapper.delete(new QueryWrapper<Sku>().eq("spu_id",spu.getId()));
        }

        //2.保存List<Sku>
        Date date = new Date();
        Category category = categoryMapper.selectById(spu.getCategoryThreeId());
        Brand brand = brandMapper.selectById(spu.getBrandId());
        for (Sku sku : product.getSkus()) {
            //SKU名稱
            String name = spu.getName();
            Map<String,String> skuattrMap = JSON.parseObject(sku.getSkuAttribute(),Map.class);
            for (Map.Entry<String, String> entry : skuattrMap.entrySet()) {
                name+="  "+entry.getValue();
            }
            sku.setName(name);
            //創建時間
            sku.setCreateTime(date);
            //修改時間
            sku.setUpdateTime(date);
            //分類ID
            sku.setCategoryId(spu.getCategoryThreeId());
            //分類名字
            sku.setBrandName(brand.getName());
            //品牌ID
            sku.setBrandId(spu.getBrandId());
            //品牌名字
            sku.setCategoryName(category.getName());
            //spuid
            sku.setSpuId(spu.getId());
            //狀態 商品狀態 1-正常,2-下架,3-刪除
            sku.setStatus(1);

            //添加
            skuMapper.insert(sku);
        }
    }
}

 

git源碼:https://gitee.com/TongHuaShuShuoWoDeJieJu/spring-cloud-alibaba1.git

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM