# MySQL 到 Elasticsearch 字段映射说明(业务版) ## 1. 概述 本文档说明 Elasticsearch 中的商品文档(Document)的各个字段是如何从 MySQL 数据库中的相关表获取和转换的。 ### 1.1 数据源表 - **SPU 表** (`shoplazza_product_spu`): 商品基本信息 - **SKU 表** (`shoplazza_product_sku`): 商品变体信息(多款式) - **Option 表** (`shoplazza_product_option`): 选项名称定义(如:颜色、尺寸) - **类目表** (`product_category`): 类目信息(通过 SPU 表的 category_id 关联) ### 1.2 数据关系 ``` SPU (1) ──→ (N) SKU # 一个商品有多个变体 SPU (1) ──→ (N) Option # 一个商品有多个选项维度 SPU.category_id ──→ 类目表 # 商品关联类目 SKU.option1/2/3 ──→ Option # SKU 的选项值对应 Option 的 position ``` --- ## 2. 基础字段映射 ### 2.1 标识字段 | ES 字段 | 数据来源表 | 表中字段 | 转换说明 | |---------|-----------|----------|----------| | `tenant_id` | SPU 表 | `tenant_id` | 直接使用,转为字符串 | | `spu_id` | SPU 表 | `id` | 直接使用,转为字符串 | ### 2.2 文本字段(多语言) 文本字段支持中英文双语,根据租户配置自动翻译。 | ES 字段 | 数据来源表 | 表中字段 | 转换说明 | |---------|-----------|----------|----------| | `title_zh` | SPU 表 | `title` | 如果主语言是中文,直接使用;否则翻译为中文 | | `title_en` | SPU 表 | `title` | 如果主语言是英文,直接使用;否则翻译为英文 | | `brief_zh` | SPU 表 | `brief` | 同上 | | `brief_en` | SPU 表 | `brief` | 同上 | | `description_zh` | SPU 表 | `description` | 同上 | | `description_en` | SPU 表 | `description` | 同上 | | `vendor_zh` | SPU 表 | `vendor` | 同上 | | `vendor_en` | SPU 表 | `vendor` | 同上 | **翻译规则:** - 根据租户配置的 `primary_language` 确定主语言 - 如果配置了 `translate_to_en=true`,且主语言不是英文,则翻译为英文 - 如果配置了 `translate_to_zh=true`,且主语言不是中文,则翻译为中文 ### 2.3 标签字段 | ES 字段 | 数据来源表 | 表中字段 | 转换说明 | |---------|-----------|----------|----------| | `tags` | SPU 表 | `tags` | 逗号分隔的字符串 → 数组
示例:`"标签1,标签2"` → `["标签1", "标签2"]` | ### 2.4 图片字段 | ES 字段 | 数据来源表 | 表中字段 | 转换说明 | |---------|-----------|----------|----------| | `image_url` | SPU 表 | `image_src` | 直接使用,如果 URL 不以 `http` 开头则添加 `//` 前缀 | ### 2.5 销量字段 | ES 字段 | 数据来源表 | 表中字段 | 转换说明 | |---------|-----------|----------|----------| | `sales` | SPU 表 | `fake_sales` | 转为整数,如果为空则为 0 | ### 2.6 时间字段 | ES 字段 | 数据来源表 | 表中字段 | 转换说明 | |---------|-----------|----------|----------| | `create_time` | SPU 表 | `create_time` | 转为 ISO 格式字符串(如:`2024-01-01T00:00:00`) | | `update_time` | SPU 表 | `update_time` | 转为 ISO 格式字符串 | --- ## 3. 类别(Category)字段映射 ### 3.1 类别数据来源 类别信息主要来自 SPU 表的以下字段: - `category_path`: 类目路径,逗号分隔的类目ID列表(如:`"1,2,3"`) - `category`: 类目名称(备用字段) - `category_id`: 当前类目ID - `category_level`: 类目层级 ### 3.2 类别映射流程 **步骤 1:解析 category_path** 从 SPU 表的 `category_path` 字段解析出类目ID列表: ``` category_path = "1,2,3" → category_ids = ["1", "2", "3"] ``` **步骤 2:ID 转名称** 通过预加载的类目映射表(从 SPU 表查询 `category_id` 和 `category` 字段构建),将ID转换为名称: ``` 映射表:{"1": "电子产品", "2": "手机", "3": "iPhone"} category_ids = ["1", "2", "3"] → category_names = ["电子产品", "手机", "iPhone"] ``` **步骤 3:构建 ES 字段** | ES 字段 | 数据来源 | 转换说明 | |---------|----------|----------| | `category_path_zh` | 类目名称列表 | 用 `/` 连接:`"电子产品/手机/iPhone"` | | `category1_name` | 类目名称列表[0] | 一级类目:`"电子产品"` | | `category2_name` | 类目名称列表[1] | 二级类目:`"手机"` | | `category3_name` | 类目名称列表[2] | 三级类目:`"iPhone"` | | `category_id` | SPU 表 | `category_id` 转为字符串 | | `category_level` | SPU 表 | `category_level` 转为整数 | **备用处理:** 如果 `category_path` 为空,使用 `category` 字段作为备选: - 如果 `category` 包含 `/`,按 `/` 分割为多级类目 - 否则,直接作为 `category1_name` **数据质量检查:** 如果 `category_path` 中的类目ID在映射表中不存在,则不写入任何类目字段(视为没有类目)。 --- ## 4. 多款式(SKU/Options)字段映射 ### 4.1 SKU 嵌套结构 一个 SPU 可以有多个 SKU,每个 SKU 代表一个商品变体(如:红色-L码、蓝色-M码)。 **ES 中的 `skus` 字段结构:** ```json { "skus": [ { "sku_id": "123", "price": 99.99, "compare_at_price": 129.99, "sku_code": "SKU001", "stock": 100, "weight": 0.5, "weight_unit": "kg", "option1_value": "红色", "option2_value": "L", "option3_value": "棉", "image_src": "https://..." } ] } ``` ### 4.2 SKU 字段映射 | ES 字段 | 数据来源表 | 表中字段 | 转换说明 | |---------|-----------|----------|----------| | `skus[].sku_id` | SKU 表 | `id` | 转为字符串 | | `skus[].price` | SKU 表 | `price` | 转为浮点数 | | `skus[].compare_at_price` | SKU 表 | `compare_at_price` | 转为浮点数 | | `skus[].sku_code` | SKU 表 | `sku` | 转为字符串 | | `skus[].stock` | SKU 表 | `inventory_quantity` | 转为整数 | | `skus[].weight` | SKU 表 | `weight` | 转为浮点数 | | `skus[].weight_unit` | SKU 表 | `weight_unit` | 转为字符串 | | `skus[].image_src` | SKU 表 | `image_src` | 转为字符串 | | `skus[].option1_value` | SKU 表 | `option1` | 转为字符串 | | `skus[].option2_value` | SKU 表 | `option2` | 转为字符串 | | `skus[].option3_value` | SKU 表 | `option3` | 转为字符串 | ### 4.3 选项名称字段 选项名称来自 Option 表,按 `position` 字段排序(1、2、3 对应 option1、option2、option3)。 | ES 字段 | 数据来源表 | 表中字段 | 转换说明 | |---------|-----------|----------|----------| | `option1_name` | Option 表 | `name` (where position=1) | 第一个选项的名称(如:"颜色") | | `option2_name` | Option 表 | `name` (where position=2) | 第二个选项的名称(如:"尺寸") | | `option3_name` | Option 表 | `name` (where position=3) | 第三个选项的名称(如:"材质") | **查询逻辑:** ```sql SELECT position, name FROM shoplazza_product_option WHERE spu_id = ? AND deleted = 0 ORDER BY position ``` ### 4.4 选项值字段 选项值来自所有 SKU 的 `option1`、`option2`、`option3` 字段,去重后形成列表。 | ES 字段 | 数据来源表 | 表中字段 | 转换说明 | |---------|-----------|----------|----------| | `option1_values` | SKU 表 | `option1` | 收集所有 SKU 的 option1 值,去重
示例:`["红色", "蓝色", "绿色"]` | | `option2_values` | SKU 表 | `option2` | 收集所有 SKU 的 option2 值,去重
示例:`["S", "M", "L"]` | | `option3_values` | SKU 表 | `option3` | 收集所有 SKU 的 option3 值,去重
示例:`["棉", "涤纶"]` | **转换逻辑:** ``` 遍历所有 SKU: - 收集 option1 值 → option1_values 列表 - 收集 option2 值 → option2_values 列表 - 收集 option3 值 → option3_values 列表 去重后写入 ES ``` **注意:** 只有配置在 `searchable_option_dimensions` 中的选项才会被索引。 ### 4.5 规格(Specifications)字段 规格字段用于支持按规格过滤和分面搜索,将 SKU 的选项值结构化存储。 **ES 中的 `specifications` 字段结构:** ```json { "specifications": [ {"sku_id": "123", "name": "颜色", "value": "红色"}, {"sku_id": "123", "name": "尺寸", "value": "L"}, {"sku_id": "124", "name": "颜色", "value": "蓝色"}, {"sku_id": "124", "name": "尺寸", "value": "M"} ] } ``` **构建逻辑:** 1. 从 Option 表获取选项名称映射: ``` position=1 → name="颜色" position=2 → name="尺寸" position=3 → name="材质" ``` 2. 遍历所有 SKU,为每个 SKU 的每个选项值构建规格记录: ``` SKU.id=123, option1="红色", option2="L" → {"sku_id": "123", "name": "颜色", "value": "红色"} → {"sku_id": "123", "name": "尺寸", "value": "L"} ``` | ES 字段 | 数据来源 | 转换说明 | |---------|----------|----------| | `specifications[].sku_id` | SKU 表 | `id` 转为字符串 | | `specifications[].name` | Option 表 | `name`(根据 position 匹配) | | `specifications[].value` | SKU 表 | `option1/2/3` 转为字符串 | ### 4.6 价格聚合字段 从所有 SKU 的价格中计算聚合值。 | ES 字段 | 数据来源 | 转换说明 | |---------|----------|----------| | `min_price` | SKU 表 | 所有 SKU 的 `price` 最小值 | | `max_price` | SKU 表 | 所有 SKU 的 `price` 最大值 | | `compare_at_price` | SKU 表 | 所有 SKU 的 `compare_at_price` 最大值 | | `sku_prices` | SKU 表 | 所有 SKU 的 `price` 数组
示例:`[99.99, 99.99, 129.99]` | **计算逻辑:** ``` 遍历所有 SKU: - 收集 price → prices 列表 - 收集 compare_at_price → compare_prices 列表 min_price = min(prices) max_price = max(prices) compare_at_price = max(compare_prices) sku_prices = prices 列表 ``` ### 4.7 库存和重量聚合字段 | ES 字段 | 数据来源 | 转换说明 | |---------|----------|----------| | `total_inventory` | SKU 表 | 所有 SKU 的 `inventory_quantity` 求和 | | `sku_weights` | SKU 表 | 所有 SKU 的 `weight` 数组
示例:`[500, 500, 600]` | | `sku_weight_units` | SKU 表 | 所有 SKU 的 `weight_unit` 去重后的列表
示例:`["kg"]` | **计算逻辑:** ``` 遍历所有 SKU: - 累加 inventory_quantity → total_inventory - 收集 weight → sku_weights 列表 - 收集 weight_unit → sku_weight_units 列表 total_inventory = 所有 SKU 库存之和 sku_weights = 所有 SKU 重量数组 sku_weight_units = 去重后的重量单位列表 ``` --- ## 5. 向量字段映射 ### 5.1 标题向量(title_embedding) | ES 字段 | 数据来源 | 转换说明 | |---------|----------|----------| | `title_embedding` | SPU 表 `title` | 使用文本编码器(BGE)将标题转换为 1024 维向量
优先使用 `title_en`,如果没有则使用 `title_zh` | **生成逻辑:** ``` 如果启用向量搜索: 文本 = title_en 或 title_zh 向量 = 文本编码器.encode(文本) title_embedding = 向量(1024 维浮点数组) ``` --- ## 6. 完整字段映射表 ### 6.1 字段来源汇总 | ES 字段 | 数据来源表 | 表中字段 | 说明 | |---------|-----------|----------|------| | **基础字段** | | `tenant_id` | SPU | `tenant_id` | 租户ID | | `spu_id` | SPU | `id` | 商品ID | | `title_zh/en` | SPU | `title` | 标题(多语言) | | `brief_zh/en` | SPU | `brief` | 简介(多语言) | | `description_zh/en` | SPU | `description` | 描述(多语言) | | `vendor_zh/en` | SPU | `vendor` | 品牌(多语言) | | `tags` | SPU | `tags` | 标签数组 | | `image_url` | SPU | `image_src` | 主图URL | | `sales` | SPU | `fake_sales` | 销量 | | `create_time` | SPU | `create_time` | 创建时间 | | `update_time` | SPU | `update_time` | 更新时间 | | **类别字段** | | `category_path_zh` | SPU + 类目映射 | `category_path` → 类目名称 | 类目路径 | | `category1_name` | SPU + 类目映射 | `category_path` → 类目名称[0] | 一级类目 | | `category2_name` | SPU + 类目映射 | `category_path` → 类目名称[1] | 二级类目 | | `category3_name` | SPU + 类目映射 | `category_path` → 类目名称[2] | 三级类目 | | `category_id` | SPU | `category_id` | 类目ID | | `category_level` | SPU | `category_level` | 类目层级 | | **SKU 嵌套字段** | | `skus[]` | SKU | 多个字段 | SKU 数组(见 4.2 节) | | **选项字段** | | `option1_name` | Option | `name` (position=1) | 选项1名称 | | `option2_name` | Option | `name` (position=2) | 选项2名称 | | `option3_name` | Option | `name` (position=3) | 选项3名称 | | `option1_values` | SKU | `option1` | 选项1值列表 | | `option2_values` | SKU | `option2` | 选项2值列表 | | `option3_values` | SKU | `option3` | 选项3值列表 | | **规格字段** | | `specifications[]` | SKU + Option | `option1/2/3` + `name` | 规格数组 | | **聚合字段** | | `min_price` | SKU | `price` | 最低价格 | | `max_price` | SKU | `price` | 最高价格 | | `compare_at_price` | SKU | `compare_at_price` | 最高原价 | | `sku_prices` | SKU | `price` | 所有价格数组 | | `total_inventory` | SKU | `inventory_quantity` | 总库存 | | `sku_weights` | SKU | `weight` | 所有重量数组 | | `sku_weight_units` | SKU | `weight_unit` | 重量单位列表 | | **向量字段** | | `title_embedding` | SPU | `title` | 标题向量(1024维) | --- ## 7. 数据查询示例 ### 7.1 查询 SPU 数据 ```sql SELECT id, tenant_id, title, brief, description, vendor, category, category_id, category_path, category_level, tags, image_src, fake_sales, create_time, update_time FROM shoplazza_product_spu WHERE tenant_id = ? AND id = ? AND deleted = 0 ``` ### 7.2 查询 SKU 数据 ```sql SELECT id, spu_id, price, compare_at_price, sku, inventory_quantity, weight, weight_unit, option1, option2, option3, image_src FROM shoplazza_product_sku WHERE tenant_id = ? AND spu_id = ? AND deleted = 0 ``` ### 7.3 查询 Option 数据 ```sql SELECT position, name FROM shoplazza_product_option WHERE tenant_id = ? AND spu_id = ? AND deleted = 0 ORDER BY position ``` ### 7.4 查询类目映射 ```sql SELECT DISTINCT category_id, category FROM shoplazza_product_spu WHERE deleted = 0 AND category_id IS NOT NULL ``` --- ## 8. ES 文档示例 ```json { "tenant_id": "1", "spu_id": "12345", "title_zh": "iPhone 15 Pro Max", "title_en": "iPhone 15 Pro Max", "brief_zh": "最新款 iPhone", "brief_en": "Latest iPhone", "description_zh": "详细描述...", "description_en": "Detailed description...", "vendor_zh": "Apple", "vendor_en": "Apple", "tags": ["手机", "智能手机", "Apple"], "category_path_zh": "电子产品/手机/iPhone", "category1_name": "电子产品", "category2_name": "手机", "category3_name": "iPhone", "category_id": "3", "category_level": 3, "option1_name": "颜色", "option2_name": "存储容量", "option1_values": ["深空黑色", "原色钛金属", "白色钛金属"], "option2_values": ["256GB", "512GB", "1TB"], "image_url": "https://example.com/image.jpg", "sales": 1000, "min_price": 8999.0, "max_price": 12999.0, "compare_at_price": 12999.0, "sku_prices": [8999.0, 10999.0, 12999.0], "sku_weights": [221, 221, 221], "sku_weight_units": ["g"], "total_inventory": 500, "create_time": "2024-01-01T00:00:00", "update_time": "2024-01-15T10:30:00", "title_embedding": [0.123, 0.456, ...], "skus": [ { "sku_id": "1001", "price": 8999.0, "compare_at_price": 9999.0, "sku_code": "IP15PM-256-BLK", "stock": 100, "weight": 221.0, "weight_unit": "g", "option1_value": "深空黑色", "option2_value": "256GB", "image_src": "https://example.com/sku1.jpg" } ], "specifications": [ {"sku_id": "1001", "name": "颜色", "value": "深空黑色"}, {"sku_id": "1001", "name": "存储容量", "value": "256GB"} ] } ``` --- ## 9. 关键映射关系总结 ### 9.1 类别映射 ``` SPU.category_path ("1,2,3") ↓ [解析ID列表] category_ids ["1", "2", "3"] ↓ [通过映射表转换] category_names ["电子产品", "手机", "iPhone"] ↓ [构建字段] category_path_zh: "电子产品/手机/iPhone" category1_name: "电子产品" category2_name: "手机" category3_name: "iPhone" ``` ### 9.2 SKU 和选项映射 ``` Option 表 (position=1, name="颜色") ↓ option1_name: "颜色" SKU 表 (option1="红色", option1="蓝色") ↓ [收集所有值,去重] option1_values: ["红色", "蓝色"] SKU 表 + Option 表 ↓ [组合构建] specifications: [ {name: "颜色", value: "红色", sku_id: "123"}, {name: "颜色", value: "蓝色", sku_id: "124"} ] ``` ### 9.3 价格聚合 ``` SKU 表 (price: 99.99, 109.99, 129.99) ↓ [聚合计算] min_price: 99.99 max_price: 129.99 sku_prices: [99.99, 109.99, 129.99] ``` --- ## 附录:数据表字段对照 ### SPU 表主要字段 | 表字段 | ES 字段 | 说明 | |--------|---------|------| | `id` | `spu_id` | 商品ID | | `tenant_id` | `tenant_id` | 租户ID | | `title` | `title_zh/en` | 标题 | | `brief` | `brief_zh/en` | 简介 | | `description` | `description_zh/en` | 描述 | | `vendor` | `vendor_zh/en` | 品牌 | | `tags` | `tags` | 标签 | | `category_path` | `category_path_zh` | 类目路径 | | `category_id` | `category_id` | 类目ID | | `category_level` | `category_level` | 类目层级 | | `image_src` | `image_url` | 主图 | | `fake_sales` | `sales` | 销量 | | `create_time` | `create_time` | 创建时间 | | `update_time` | `update_time` | 更新时间 | ### SKU 表主要字段 | 表字段 | ES 字段 | 说明 | |--------|---------|------| | `id` | `skus[].sku_id` | SKU ID | | `price` | `skus[].price`, `min_price`, `max_price`, `sku_prices` | 价格 | | `compare_at_price` | `skus[].compare_at_price`, `compare_at_price` | 原价 | | `sku` | `skus[].sku_code` | SKU编码 | | `inventory_quantity` | `skus[].stock`, `total_inventory` | 库存 | | `weight` | `skus[].weight`, `sku_weights` | 重量 | | `weight_unit` | `skus[].weight_unit`, `sku_weight_units` | 重量单位 | | `option1` | `skus[].option1_value`, `option1_values`, `specifications[].value` | 选项1值 | | `option2` | `skus[].option2_value`, `option2_values`, `specifications[].value` | 选项2值 | | `option3` | `skus[].option3_value`, `option3_values`, `specifications[].value` | 选项3值 | | `image_src` | `skus[].image_src` | SKU图片 | ### Option 表主要字段 | 表字段 | ES 字段 | 说明 | |--------|---------|------| | `position` | - | 位置(1/2/3) | | `name` | `option1_name`, `option2_name`, `option3_name`, `specifications[].name` | 选项名称 |