SKU设计

分类: 科技创建于: 6/6/2025

好的,我们来更详细地拆解SKU(Stock Keeping Unit)设计,特别是如何优雅地处理不同商品品类(如服装和电子产品)拥有完全不同属性集的问题。

SKU设计的核心原理:变体与属性分离

核心思想:
不要尝试在商品主表 (products) 中为所有可能的属性(如颜色、尺码、内存、CPU)都创建列。这会导致表结构极其庞大且难以维护,因为大部分列对特定商品来说都是空白的。

相反,我们采取**“通用属性池 + 灵活组合”**的策略:

  1. 通用属性池: 创建独立的属性类型 (attributes) 和属性值 (attribute_values) 表,它们存储了系统中所有商品可能用到的所有属性的类型和具体值。
  2. 商品变体(SKU): 针对每个商品,其具体的、可购买的最小单位(SKU)是根据其所拥有的一组特定属性值来定义的。
  3. 灵活组合: 通过一个中间关联表 (product_variant_attributes),将每个SKU与它所包含的那些特定属性值关联起来。这样,一个服装SKU只关联“颜色”和“尺码”的属性值,而一个手机SKU则关联“存储容量”、“颜色”和“运行内存”的属性值。

核心表结构深入理解

我们再次回顾并深入理解之前提供的关键表结构:

  1. products (商品主表):

    • 存储商品通用信息,这些信息不随SKU变化。例如:商品名称、描述、品牌、主图、商品分类、自由属性 (custom_attributes JSONB)
    • custom_attributes (JSONB类型) 是一个非常重要的字段,它用于存储那些不作为SKU维度的额外、灵活的属性。比如:
      • 服装:材质、适用季节、领型、袖长。
      • 电子产品:屏幕尺寸、摄像头像素、操作系统版本、接口类型。
      • 特点: 这些属性对同一商品的所有SKU都一样,或者说它们只是描述性的,不参与SKU的组合。
  2. product_variants (商品变体/SKU表):

    • 存储每个具体可购买的最小单位的信息。
    • 每个 product_variant 记录代表一个唯一的SKU,它有自己的sku编码、pricestock、甚至自己专属的image_url
    • 重点: product_variants 表本身不直接存储属性值。它只是一个“占位符”,表示一个SKU存在。
  3. attributes (属性类型表):

    • 定义属性的“种类”或“维度”。这是一个全局性的列表,包含了电商平台中所有商品可能用到的所有属性类型。
    • 例如:id=1, name='颜色', type='color'
    • 例如:id=2, name='尺码', type='text'
    • 例如:id=3, name='存储容量', type='text'
    • 例如:id=4, name='运行内存', type='text'
    • 例如:id=5, name='CPU型号', type='text'
  4. attribute_values (属性值表):

    • 存储每个属性类型下具体可选的值。这也是一个全局性的列表。
    • 例如:
      • id=101, attribute_id=1 (颜色), value='#FF0000', display_name='红色'
      • id=102, attribute_id=1 (颜色), value='#0000FF', display_name='蓝色'
      • id=201, attribute_id=2 (尺码), value='S', display_name='小码'
      • id=202, attribute_id=2 (尺码), value='M', display_name='中码'
      • id=301, attribute_id=3 (存储容量), value='128GB', display_name='128GB'
      • id=302, attribute_id=3 (存储容量), value='256GB', display_name='256GB'
  5. product_variant_attributes (商品变体-属性值关联表):

    • 这是核心的连接点。它将 product_variantsattribute_values 关联起来。
    • 原理: 每个SKU由一组特定的属性值组合而成。这个表就记录了“SKU X 包含了 属性值 A 和 属性值 B”。
    • 例如:
      • product_variant_id=SKU_ID_FOR_RED_S_TSHIRT, attribute_value_id=101 (红色)
      • product_variant_id=SKU_ID_FOR_RED_S_TSHIRT, attribute_value_id=201 (小码)
      • product_variant_id=SKU_ID_FOR_256GB_BLACK_PHONE, attribute_value_id=302 (256GB)
      • product_variant_id=SKU_ID_FOR_256GB_BLACK_PHONE, attribute_value_id=103 (黑色, 假设ID为103)
      • product_variant_id=SKU_ID_FOR_256GB_BLACK_PHONE, attribute_value_id=401 (8GB, 假设ID为401)

如何应对不同商品的属性差异?

这就是这套设计的精妙之处:

1. 统一的属性管理 (后端视角):
无论是服装还是电子产品,它们共用 attributesattribute_values 这两个“属性池”。当需要添加一个新的属性(比如“洗涤方式”或者“电池容量”),只需要在这两个表中添加新的条目即可。

2. SKU级别的属性定义 (后端视角):
当创建一个具体商品(例如一件T恤或一部手机)时,你并不是为 product 定义它有哪些属性,而是为它的每个SKU定义它由哪些属性值组成。

  • 服装类商品示例:T恤

    • products 表:
      • id: 1, name: '时尚圆领T恤', description: '舒适纯棉T恤', category_id: 1 (服装), custom_attributes: {"材质": "纯棉", "领型": "圆领"}
    • attributes 表中需要存在: 颜色, 尺码
    • attribute_values 表中需要存在:
      • 颜色: 红色, 蓝色
      • 尺码: S, M, L
    • product_variants 表 (部分SKU):
      • id: 101, product_id: 1, sku: 'TSHIRT-RED-S', price: 99.00, stock: 50
      • id: 102, product_id: 1, sku: 'TSHIRT-RED-M', price: 109.00, stock: 30
      • id: 103, product_id: 1, sku: 'TSHIRT-BLUE-S', price: 99.00, stock: 40
    • product_variant_attributes 表 (关联这些SKU和属性值):
      • product_variant_id: 101, attribute_value_id: (红色ID)
      • product_variant_id: 101, attribute_value_id: (S码ID)
      • product_variant_id: 102, attribute_value_id: (红色ID)
      • product_variant_id: 102, attribute_value_id: (M码ID)
      • product_variant_id: 103, attribute_value_id: (蓝色ID)
      • product_variant_id: 103, attribute_value_id: (S码ID)
      • ...
    • 结果: 对于这件T恤,它只有“颜色”和“尺码”作为可变属性。
  • 电子产品示例:智能手机

    • products 表:
      • id: 2, name: 'Pro Max 智能手机', description: '极致性能,超强续航', category_id: 2 (电子产品), custom_attributes: {"屏幕尺寸": "6.7英寸", "电池容量": "4000mAh", "充电接口": "Type-C"}
    • attributes 表中需要存在: 颜色, 存储容量, 运行内存, CPU型号
    • attribute_values 表中需要存在:
      • 颜色: 黑色, 白色, 金色
      • 存储容量: 128GB, 256GB, 512GB
      • 运行内存: 8GB, 12GB
      • CPU型号: Snapdragon 8 Gen 2, Apple A16 Bionic
    • product_variants 表 (部分SKU):
      • id: 201, product_id: 2, sku: 'PHONE-BLK-128GB-8GB', price: 5999.00, stock: 20
      • id: 202, product_id: 2, sku: 'PHONE-WHT-256GB-12GB', price: 7999.00, stock: 15
      • ...
    • product_variant_attributes 表 (关联这些SKU和属性值):
      • product_variant_id: 201, attribute_value_id: (黑色ID)
      • product_variant_id: 201, attribute_value_id: (128GB ID)
      • product_variant_id: 201, attribute_value_id: (8GB ID)
      • product_variant_id: 202, attribute_value_id: (白色ID)
      • product_variant_id: 202, attribute_value_id: (256GB ID)
      • product_variant_id: 202, attribute_value_id: (12GB ID)
      • ...
    • 结果: 对于这款手机,它有“颜色”、“存储容量”和“运行内存”作为可变属性。

关键点:
后端在查询某个商品的详情时,会执行复杂的JOIN查询,从 products 表开始,通过 product_variants 表找到所有SKU,再通过 product_variant_attributesattribute_values 表,聚合出这个商品所有可用变体以及所有涉及到的属性类型和属性值

后端API如何返回这些信息

API响应中的 available_attributes 字段是后端根据 product.variantsproduct_variant_attributes 动态计算生成的。

例如,对于上面的T恤:
后端会扫描所有T恤的SKU,发现它们共同涉及到了 颜色尺码 这两种属性,以及它们对应的 红色, 蓝色, S, M, L 这些具体值。然后将其组织成 available_attributes 返回给前端。

对于手机:
后端会扫描所有手机的SKU,发现它们共同涉及到了 颜色, 存储容量, 运行内存 这三种属性,以及它们对应的 黑色, 白色, 金色, 128GB, 256GB, 512GB, 8GB, 12GB 这些具体值。然后将其组织成 available_attributes 返回给前端。

前端如何渲染

前端接收到这种聚合数据后,会:

  1. 渲染 product.name, product.description 等通用信息。
  2. 渲染 product.custom_attributes (JSONB) 作为商品详情的额外描述。 例如,对于T恤,显示“材质:纯棉”;对于手机,显示“屏幕尺寸:6.7英寸”。这些只是文本展示,不会有选择器。
  3. 遍历 product.available_attributes 数组,动态生成属性选择器。
    • 如果 attribute.name 是“颜色”,就渲染颜色块。
    • 如果 attribute.name 是“尺码”、“存储容量”等,就渲染按钮或下拉框。
    • 这样,前端无需知道商品具体是什么品类,只需要根据后端提供的 available_attributes 结构来渲染对应的属性选择UI。

总结

这种设计模式的优点:

  • 极度灵活: 可以轻松添加新的属性类型,无需修改数据库表结构。
  • 高度可扩展: 任何商品都可以拥有任意数量和类型的变体属性。
  • 数据一致性: 属性值统一管理,避免重复和不一致。
  • 前端通用性: 前端代码可以通用化,只需根据后端API返回的数据动态渲染,无需为不同商品品类写不同的UI逻辑。

通过这种“属性池 + SKU组合”的设计,就能完美应对拥有不同尺码、不同颜色、不同自由属性(以及各种其他可变属性)的商品的复杂前后端设计需求。